<commit_before>#include "config.h"
#include "netserver.h"
#include "factory.h"
#include "connection_pool.h"
#include "program_launcher.h"

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

#include <signal.h>

#ifdef _WIN32
#include <Windows.h>
#endif

#include <liblog.h>
#include <yaml-cpp/yaml.h>

MODULE_LOG_NAME("Main");

TConfig g_configSvr;
TPolicy g_configPolicy;

#ifdef _WIN32
HANDLE g_hExitEvent;
#endif

std::string int2str(int num)
{
	std::stringstream sstream;
	sstream << num;
	return sstream.str();
}

void loadDefaultConfig()
{
	g_configSvr.nTLS = 0;
	g_configSvr.nThreadPerCPU = 2;
	g_configSvr.nSessionTimeout = 300;
	g_configSvr.strMySQLHost = "localhost";
	g_configSvr.nMySQLPort = 3306;
	g_configSvr.strSchema = "librother";
	g_configSvr.strMySQLDIR = "./mysql/";
	g_configSvr.nSphinxType = 0;
	g_configSvr.strMySQLHost = "localhost";
	g_configSvr.nSphinxPort = 9306;
	g_configSvr.strSphinxDIR = "./sphinx/";
#ifdef _WIN32
	g_configSvr.strPathSearchd = "searchd.exe";
	g_configSvr.strPathIndexer = "indexer.exe";
	g_configSvr.strPathMysqld = "mysqld.exe";
	g_configSvr.strPathMysqlAdmin = "mysqladmin.exe";
#else
	g_configSvr.strPathSearchd = "searchd";
	g_configSvr.strPathIndexer = "indexer";
	g_configSvr.strPathMysqld = "mysqld";
	g_configSvr.strPathMysqlAdmin = "mysqladmin";
#endif
}

bool loadConfig(const std::string& strFile)
{
	std::ifstream fconfig;
	fconfig.open(strFile, std::ios::in);
	if (!fconfig.is_open())
		return false;

	try
	{
		std::string temp;
		YAML::Node doc = YAML::Load(fconfig);
		YAML::Node network = doc["network"];
		g_configSvr.strServerIP = network["host"].as<std::string>();
		g_configSvr.nPort = network["port"].as<int>();
		if (network["tls"].IsDefined())
		{
			temp = network["tls"].as<std::string>();
			std::transform(temp.begin(), temp.end(), temp.begin(), toupper);
			if (temp == "ENABLED")
				g_configSvr.nTLS = 1;
			else if (temp == "FORCED")
				g_configSvr.nTLS = 2;
			else
				g_configSvr.nTLS = 0;
		}
		if (g_configSvr.nTLS)
		{
			g_configSvr.nTLSPort = network["tls-port"].as<int>();

			YAML::Node secure = doc["secure"];
			for (int i = 0; i < secure.size(); i++)
			{
				TCertFile cert;
				cert.strCert = secure[i]["cert"].as<std::string>();
				cert.strKey = secure[i]["key"].as<std::string>();
				cert.strPassphrase = secure[i]["passphrase"].as<std::string>();
				g_configSvr.vCerts.push_back(cert);
			}
		}

		YAML::Node performance = doc["performance"];
		if (performance.IsDefined())
		{
			if (performance["thread-per-cpu"].IsDefined())
				g_configSvr.nThreadPerCPU = performance["thread-per-cpu"].as<int>();
			if (performance["session-timeout"].IsDefined())
				g_configSvr.nSessionTimeout = performance["session-timeout"].as<int>();
		}

		YAML::Node policy = doc["policy"];
		if (policy.IsDefined())
		{
			if (policy["license-file"].IsDefined())
			{
				std::ifstream flicense;
				flicense.open(policy["license-file"].as<std::string>());
				if (flicense.is_open())
				{
					std::stringstream sstream;
					sstream << flicense.rdbuf();
					g_configPolicy.strLicense = sstream.str();
					flicense.close();
				}
			}
			//TODO: Other Policy Config
		}

		YAML::Node mysql = doc["mysql"];
		temp = mysql["type"].as<std::string>();
		std::transform(temp.begin(), temp.end(), temp.begin(), toupper);
		if (temp == "LOCAL")
			g_configSvr.nMySQLType = 1;
		else if (temp == "REMOTE")
			g_configSvr.nMySQLType = 2;
		else
			return false;
		g_configSvr.strUser = mysql["user"].as<std::string>();
		g_configSvr.strPWD = mysql["password"].as<std::string>();
		if (mysql["host"].IsDefined())
			g_configSvr.strMySQLHost = mysql["host"].as<std::string>();
		if (mysql["port"].IsDefined())
			g_configSvr.nMySQLPort = mysql["port"].as<int>();
		if (mysql["schema"].IsDefined())
			g_configSvr.strSchema = mysql["schema"].as<std::string>();
		if (g_configSvr.nMySQLType == 1)
		{
			if (mysql["mysqld-bin"].IsDefined())
				g_configSvr.strPathMysqld = mysql["mysqld-bin"].as<std::string>();
			if (mysql["mysqladmin-bin"].IsDefined())
				g_configSvr.strPathMysqlAdmin = mysql["mysqladmin-bin"].as<std::string>();
			if (mysql["dir"].IsDefined())
				g_configSvr.strMySQLDIR = mysql["dir"].as<std::string>();
		}

		YAML::Node sphinx = doc["sphinx"];
		if (sphinx.IsDefined())
		{
			temp = sphinx["type"].as<std::string>();
			std::transform(temp.begin(), temp.end(), temp.begin(), toupper);
			if (temp == "LOCAL")
				g_configSvr.nSphinxType = 1;
			else if (temp == "REMOTE")
				g_configSvr.nSphinxType = 2;
			else
				g_configSvr.nSphinxType = 0;
			if (sphinx["host"].IsDefined())
				g_configSvr.strSphinxHost = sphinx["host"].as<std::string>();
			if (sphinx["port"].IsDefined())
				g_configSvr.nSphinxPort = sphinx["port"].as<int>();
			if (g_configSvr.nSphinxType == 1)
			{
				if (sphinx["searchd-bin"].IsDefined())
					g_configSvr.strPathSearchd = sphinx["searchd-bin"].as<std::string>();
				if (sphinx["indexer-bin"].IsDefined())
					g_configSvr.strPathIndexer = sphinx["indexer-bin"].as<std::string>();
				if (sphinx["dir"].IsDefined())
					g_configSvr.strSphinxDIR = sphinx["dir"].as<std::string>();
			}
		}
	}
	catch (std::exception& e)
	{
		lprintf_e("An error occurred while parsing config file: \n%s", e.what());
		return false;
	}
	return true;
}

bool initMySQL(CConnectionPool& connPool)
{
	std::string connURL = "tcp://";
	connURL += g_configSvr.strMySQLHost;
	connURL += ":";
	connURL += g_configSvr.nMySQLPort;
	return connPool.registerConnection(
		REGID_MYSQL_CONN, connURL,
		g_configSvr.strUser, g_configSvr.strPWD,
		g_configSvr.strSchema);
}

bool initSphinx(CConnectionPool& connPool)
{
	std::string connURL = "tcp://";
	connURL += g_configSvr.strSphinxHost;
	connURL += ":";
	connURL += g_configSvr.nSphinxPort;
	return connPool.registerConnection(
		REGID_SPHINX_CONN, connURL, "", "", "");
}

void stopSphinx(CProgramLauncher& progLauncher, int nSearchd, int nIndexer)
{
	if(nIndexer >= 0)
		progLauncher.stopProgram(nIndexer, -1, false);
	if (nSearchd >= 0)
	{
		std::vector<std::string> vecArgs;
		vecArgs.push_back("--stop");
		progLauncher.runProgram(
			g_configSvr.strPathSearchd, vecArgs, g_configSvr.strSphinxDIR,
			CProgramLauncher::RunWait);
		progLauncher.stopProgram(nSearchd, 30000, true);
	}
}

void stopMysql(CProgramLauncher& progLauncher, int nMysqld)
{
	if (nMysqld < 0)
		return;
	std::vector<std::string> vecArgs;
	vecArgs.push_back(std::string("--host=") + g_configSvr.strMySQLHost);
	vecArgs.push_back(std::string("--port=") + int2str(g_configSvr.nMySQLPort));
	vecArgs.push_back(std::string("--user=") + g_configSvr.strUser);
	vecArgs.push_back(std::string("--password=") + g_configSvr.strPWD);
	vecArgs.push_back("shutdown");
	progLauncher.runProgram(
		g_configSvr.strPathMysqlAdmin, vecArgs, g_configSvr.strMySQLDIR,
		CProgramLauncher::RunWait);
	progLauncher.stopProgram(nMysqld, 30000, true);
}

void signalHandler(int sig)
{
	if (sig == SIGINT)
	{
		std::cout << "SIGINT received" << std::endl;
		SetEvent(g_hExitEvent);
	}
}

void waitForSigint()
{
#ifdef _WIN32
	g_hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	signal(SIGINT, signalHandler);
	WaitForSingleObject(g_hExitEvent, INFINITE);
	CloseHandle(g_hExitEvent);
#endif
}

int main(int argc, char * argv[])
{
	//TODO: parse cmdline
	InitLog("server.log");
	lprintf("Initializing LiBrother Server.");
	if (!loadConfig("server.yaml"))
	{
		lprintf_e("Failed to load config");
		CloseLog();
		return 1;
	}

	CProgramLauncher progLauncher;
	CConnectionPool connPool;

	int nMysqld = -1, nSearchd = -1, nIndexer = -1;
	if (g_configSvr.nMySQLType == 1)
	{
		nMysqld = progLauncher.runProgram(
			g_configSvr.strPathMysqld, std::vector<std::string>(), g_configSvr.strMySQLDIR,
			CProgramLauncher::RunBackend);
		if (nMysqld < 0)
		{
			lprintf_e("Failed to execute mysqld");
			CloseLog();
			return 2;
		}
	}
	if (!initMySQL(connPool))
	{
		lprintf_e("Failed to init MySQL");
		stopMysql(progLauncher, nMysqld);
		CloseLog();
		return 3;
	}

	if (g_configSvr.nSphinxType == 1)
	{
		nSearchd = progLauncher.runProgram(
			g_configSvr.strPathSearchd, std::vector<std::string>(), g_configSvr.strSphinxDIR,
			CProgramLauncher::RunBackend);
		if (nSearchd < 0)
		{
			lprintf_w("Failed to execute searchd, will disable sphinx support");
			g_configSvr.nSphinxType = 0;
		}
		else
		{
			//TODO: Launch Indexer
			std::vector<std::string> vecArgs;
			vecArgs.push_back("book_delta");
			vecArgs.push_back("--rotate");
			nIndexer = progLauncher.runProgram(
				g_configSvr.strPathIndexer, vecArgs, g_configSvr.strSphinxDIR,
				CProgramLauncher::RunAsTask, 300000);
			if (nIndexer < 0)
			{
				lprintf_w("Failed to add task Indexer");
			}
		}
	}
	if (g_configSvr.nSphinxType)
	{
		if (!initSphinx(connPool))
		{
			lprintf_w("Failed to init Sphinx");
			stopSphinx(progLauncher, nSearchd, nIndexer);
			nSearchd = nIndexer = -1;
		}
	}

	CNetServer netServer;
	if (!netServer.initNetServer())
	{
		lprintf_e("Failed to init NetServer");
		stopSphinx(progLauncher, nSearchd, nIndexer);
		stopMysql(progLauncher, nMysqld);
		CloseLog();
		return 4;
	}
	Cfactory *pClassFactory = new Cfactory(&connPool);
	pClassFactory->AddRef();
	if (!netServer.startServer(dynamic_cast<ILibClassFactory*>(pClassFactory)))
	{
		lprintf_e("Failed to start NetServer");
		delete pClassFactory;
		stopSphinx(progLauncher, nSearchd, nIndexer);
		stopMysql(progLauncher, nMysqld);
		CloseLog();
		return 5;
	}
	lprintf("Server started successfully.");

	waitForSigint();

	lprintf("Stopping server");
	netServer.stopServer();
	delete pClassFactory;
	stopSphinx(progLauncher, nSearchd, nIndexer);
	stopMysql(progLauncher, nMysqld);
	lprintf("Server stopped");

	CloseLog();
	return 0;
}<commit_msg>一些修正 & 添加注释<commit_after>#include "config.h"
#include "netserver.h"
#include "factory.h"
#include "connection_pool.h"
#include "program_launcher.h"

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

#include <signal.h>

#ifdef _WIN32
#include <Windows.h>
#endif

#include <liblog.h>
#include <yaml-cpp/yaml.h>

MODULE_LOG_NAME("Main");

TConfig g_configSvr;
TPolicy g_configPolicy;

#ifdef _WIN32
HANDLE g_hExitEvent;
#endif

std::string int2str(int num)
{
	std::stringstream sstream;
	sstream << num;
	return sstream.str();
}

//Ĭ
void loadDefaultConfig()
{
	g_configSvr.nTLS = 0;
	g_configSvr.nThreadPerCPU = 2;
	g_configSvr.nSessionTimeout = 300;
	g_configSvr.strMySQLHost = "localhost";
	g_configSvr.nMySQLPort = 3306;
	g_configSvr.strSchema = "librother";
	g_configSvr.strMySQLDIR = "./mysql/";
	g_configSvr.nSphinxType = 0;
	g_configSvr.strMySQLHost = "localhost";
	g_configSvr.nSphinxPort = 9306;
	g_configSvr.strSphinxDIR = "./sphinx/";
#ifdef _WIN32
	g_configSvr.strPathSearchd = "searchd.exe";
	g_configSvr.strPathIndexer = "indexer.exe";
	g_configSvr.strPathMysqld = "mysqld.exe";
	g_configSvr.strPathMysqlAdmin = "mysqladmin.exe";
#else
	g_configSvr.strPathSearchd = "searchd";
	g_configSvr.strPathIndexer = "indexer";
	g_configSvr.strPathMysqld = "mysqld";
	g_configSvr.strPathMysqlAdmin = "mysqladmin";
#endif
	g_configPolicy.nDefaultUserReadLevel = 10;
	g_configPolicy.nDefaultBookReadLevel = 10;
	g_configPolicy.nDefaultUserAuthLevel = 0;
	g_configPolicy.vAuthList.push_back({ 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 });
	g_configPolicy.vAuthList.push_back({ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 });
	g_configPolicy.vAuthList.push_back({ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
}

//ļ
bool loadConfig(const std::string& strFile)
{
	std::ifstream fconfig;
	fconfig.open(strFile, std::ios::in);
	if (!fconfig.is_open())
		return false;

	try
	{
		std::string temp;
		YAML::Node doc = YAML::Load(fconfig);
		YAML::Node network = doc["network"];
		g_configSvr.strServerIP = network["host"].as<std::string>();
		g_configSvr.nPort = network["port"].as<int>();
		if (network["tls"].IsDefined())
		{
			temp = network["tls"].as<std::string>();
			std::transform(temp.begin(), temp.end(), temp.begin(), toupper);
			if (temp == "ENABLED")
				g_configSvr.nTLS = 1;
			else if (temp == "FORCED")
				g_configSvr.nTLS = 2;
			else
				g_configSvr.nTLS = 0;
		}
		if (g_configSvr.nTLS)
		{
			g_configSvr.nTLSPort = network["tls-port"].as<int>();

			YAML::Node secure = doc["secure"];
			for (int i = 0; i < secure.size(); i++)
			{
				TCertFile cert;
				cert.strCert = secure[i]["cert"].as<std::string>();
				cert.strKey = secure[i]["key"].as<std::string>();
				cert.strPassphrase = secure[i]["passphrase"].as<std::string>();
				g_configSvr.vCerts.push_back(cert);
			}
		}

		YAML::Node performance = doc["performance"];
		if (performance.IsDefined())
		{
			if (performance["thread-per-cpu"].IsDefined())
				g_configSvr.nThreadPerCPU = performance["thread-per-cpu"].as<int>();
			if (performance["session-timeout"].IsDefined())
				g_configSvr.nSessionTimeout = performance["session-timeout"].as<int>();
		}

		YAML::Node policy = doc["policy"];
		if (policy.IsDefined())
		{
			if (policy["license-file"].IsDefined())
			{
				std::ifstream flicense;
				flicense.open(policy["license-file"].as<std::string>());
				if (flicense.is_open())
				{
					std::stringstream sstream;
					sstream << flicense.rdbuf();
					g_configPolicy.strLicense = sstream.str();
					flicense.close();
				}
			}
			//TODO: Other Policy Config
		}

		YAML::Node mysql = doc["mysql"];
		temp = mysql["type"].as<std::string>();
		std::transform(temp.begin(), temp.end(), temp.begin(), toupper);
		if (temp == "LOCAL")
			g_configSvr.nMySQLType = 1;
		else if (temp == "REMOTE")
			g_configSvr.nMySQLType = 2;
		else
			return false;
		g_configSvr.strUser = mysql["user"].as<std::string>();
		g_configSvr.strPWD = mysql["password"].as<std::string>();
		if (mysql["host"].IsDefined())
			g_configSvr.strMySQLHost = mysql["host"].as<std::string>();
		if (mysql["port"].IsDefined())
			g_configSvr.nMySQLPort = mysql["port"].as<int>();
		if (mysql["schema"].IsDefined())
			g_configSvr.strSchema = mysql["schema"].as<std::string>();
		if (g_configSvr.nMySQLType == 1)
		{
			if (mysql["mysqld-bin"].IsDefined())
				g_configSvr.strPathMysqld = mysql["mysqld-bin"].as<std::string>();
			if (mysql["mysqladmin-bin"].IsDefined())
				g_configSvr.strPathMysqlAdmin = mysql["mysqladmin-bin"].as<std::string>();
			if (mysql["dir"].IsDefined())
				g_configSvr.strMySQLDIR = mysql["dir"].as<std::string>();
		}

		YAML::Node sphinx = doc["sphinx"];
		if (sphinx.IsDefined())
		{
			temp = sphinx["type"].as<std::string>();
			std::transform(temp.begin(), temp.end(), temp.begin(), toupper);
			if (temp == "LOCAL")
				g_configSvr.nSphinxType = 1;
			else if (temp == "REMOTE")
				g_configSvr.nSphinxType = 2;
			else
				g_configSvr.nSphinxType = 0;
			if (sphinx["host"].IsDefined())
				g_configSvr.strSphinxHost = sphinx["host"].as<std::string>();
			if (sphinx["port"].IsDefined())
				g_configSvr.nSphinxPort = sphinx["port"].as<int>();
			if (g_configSvr.nSphinxType == 1)
			{
				if (sphinx["searchd-bin"].IsDefined())
					g_configSvr.strPathSearchd = sphinx["searchd-bin"].as<std::string>();
				if (sphinx["indexer-bin"].IsDefined())
					g_configSvr.strPathIndexer = sphinx["indexer-bin"].as<std::string>();
				if (sphinx["dir"].IsDefined())
					g_configSvr.strSphinxDIR = sphinx["dir"].as<std::string>();
			}
		}
	}
	catch (std::exception& e)
	{
		lprintf_e("An error occurred while parsing config file: \n%s", e.what());
		return false;
	}
	return true;
}

//ʼMySQL
bool initMySQL(CConnectionPool& connPool)
{
	std::string connURL = "tcp://";
	connURL += g_configSvr.strMySQLHost;
	connURL += ":";
	connURL += g_configSvr.nMySQLPort;
	return connPool.registerConnection(
		REGID_MYSQL_CONN, connURL,
		g_configSvr.strUser, g_configSvr.strPWD,
		g_configSvr.strSchema);
}

//ʼSphinxQL
bool initSphinx(CConnectionPool& connPool)
{
	std::string connURL = "tcp://";
	connURL += g_configSvr.strSphinxHost;
	connURL += ":";
	connURL += g_configSvr.nSphinxPort;
	return connPool.registerConnection(
		REGID_SPHINX_CONN, connURL, "", "", "");
}

//ֹͣSphinx
void stopSphinx(CProgramLauncher& progLauncher, int nSearchd, int nIndexer)
{
	if(nIndexer >= 0)
		progLauncher.stopProgram(nIndexer, -1, false);
	if (nSearchd >= 0)
	{
		std::vector<std::string> vecArgs;
		vecArgs.push_back("--stop");
		progLauncher.runProgram(
			g_configSvr.strPathSearchd, vecArgs, g_configSvr.strSphinxDIR,
			CProgramLauncher::RunWait);
		progLauncher.stopProgram(nSearchd, 30000, true);
	}
}

//ֹͣMySQL
void stopMysql(CProgramLauncher& progLauncher, int nMysqld)
{
	if (nMysqld < 0)
		return;
	std::vector<std::string> vecArgs;
	vecArgs.push_back(std::string("--host=") + g_configSvr.strMySQLHost);
	vecArgs.push_back(std::string("--port=") + int2str(g_configSvr.nMySQLPort));
	vecArgs.push_back(std::string("--user=") + g_configSvr.strUser);
	vecArgs.push_back(std::string("--password=") + g_configSvr.strPWD);
	vecArgs.push_back("shutdown");
	progLauncher.runProgram(
		g_configSvr.strPathMysqlAdmin, vecArgs, g_configSvr.strMySQLDIR,
		CProgramLauncher::RunWait);
	progLauncher.stopProgram(nMysqld, 30000, true);
}

void signalHandler(int sig)
{
	if (sig == SIGINT)
	{
		std::cout << "SIGINT received" << std::endl;
		SetEvent(g_hExitEvent);
	}
}

//ȴ
void waitForSigint()
{
#ifdef _WIN32
	g_hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	signal(SIGINT, signalHandler);
	WaitForSingleObject(g_hExitEvent, INFINITE);
	CloseHandle(g_hExitEvent);
#endif
}

int main(int argc, char * argv[])
{
	//TODO: parse cmdline
	InitLog("server.log");
	lprintf("Initializing LiBrother Server.");
	if (!loadConfig("server.yaml"))
	{
		lprintf_e("Failed to load config");
		CloseLog();
		return 1;	//ļʧ
	}

	CProgramLauncher progLauncher;
	CConnectionPool connPool;

	int nMysqld = -1, nSearchd = -1, nIndexer = -1;
	if (g_configSvr.nMySQLType == 1)
	{
		nMysqld = progLauncher.runProgram(
			g_configSvr.strPathMysqld, std::vector<std::string>(), g_configSvr.strMySQLDIR,
			CProgramLauncher::RunBackend);
		if (nMysqld < 0)
		{
			lprintf_e("Failed to execute mysqld");
			CloseLog();
			return 2;	//MySQLʧ
		}
	}
	if (!initMySQL(connPool))
	{
		lprintf_e("Failed to init MySQL");
		stopMysql(progLauncher, nMysqld);
		CloseLog();
		return 3;	//MySQLʧ
	}

	if (g_configSvr.nSphinxType == 1)
	{
		nSearchd = progLauncher.runProgram(
			g_configSvr.strPathSearchd, std::vector<std::string>(), g_configSvr.strSphinxDIR,
			CProgramLauncher::RunBackend);
		if (nSearchd < 0)
		{
			lprintf_w("Failed to execute searchd, will disable sphinx support");
			g_configSvr.nSphinxType = 0;
		}
		else
		{
			//TODO: Launch Indexer
			std::vector<std::string> vecArgs;
			vecArgs.push_back("book_delta");
			vecArgs.push_back("--rotate");
			nIndexer = progLauncher.runProgram(
				g_configSvr.strPathIndexer, vecArgs, g_configSvr.strSphinxDIR,
				CProgramLauncher::RunAsTask, 300000);
			if (nIndexer < 0)
			{
				lprintf_w("Failed to add task Indexer");
			}
		}
	}
	if (g_configSvr.nSphinxType)
	{
		if (!initSphinx(connPool))
		{
			lprintf_w("Failed to init Sphinx");
			stopSphinx(progLauncher, nSearchd, nIndexer);
			nSearchd = nIndexer = -1;
		}
	}

	CNetServer netServer;
	if (!netServer.initNetServer())
	{
		lprintf_e("Failed to init NetServer");
		stopSphinx(progLauncher, nSearchd, nIndexer);
		stopMysql(progLauncher, nMysqld);
		CloseLog();
		return 4;	//ʼʧ
	}
	Cfactory *pClassFactory = new Cfactory(&connPool);
	pClassFactory->AddRef();
	if (!netServer.startServer(dynamic_cast<ILibClassFactory*>(pClassFactory)))
	{
		lprintf_e("Failed to start NetServer");
		delete pClassFactory;
		stopSphinx(progLauncher, nSearchd, nIndexer);
		stopMysql(progLauncher, nMysqld);
		CloseLog();
		return 5;	//ʧ
	}
	lprintf("Server started successfully.");

	waitForSigint();

	lprintf("Stopping server");
	netServer.stopServer();
	delete pClassFactory;
	stopSphinx(progLauncher, nSearchd, nIndexer);
	stopMysql(progLauncher, nMysqld);
	lprintf("Server stopped");

	CloseLog();
	return 0;
}<|endoftext|>


<commit_before>/*************************************************************************
 *                                                                       *
 * Open Dynamics Engine, Copyright (C) 2001,2002 Russell L. Smith.       *
 * All rights reserved.  Email: russ@q12.org   Web: www.q12.org          *
 *                                                                       *
 * This library is free software; you can redistribute it and/or         *
 * modify it under the terms of EITHER:                                  *
 *   (1) 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. The text of the GNU Lesser      *
 *       General Public License is included with this library in the     *
 *       file LICENSE.TXT.                                               *
 *   (2) The BSD-style license that is included with this library in     *
 *       the file LICENSE-BSD.TXT.                                       *
 *                                                                       *
 * 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 files    *
 * LICENSE.TXT and LICENSE-BSD.TXT for more details.                     *
 *                                                                       *
 *************************************************************************/

#include <ode/ode.h>
#include <drawstuff/drawstuff.h>

#ifdef MSVC
#pragma warning(disable:4244 4305)  // for VC++, no precision loss complaints
#endif

// select correct drawing functions

#ifdef dDOUBLE
#define dsDrawBox dsDrawBoxD
#define dsDrawSphere dsDrawSphereD
#define dsDrawCylinder dsDrawCylinderD
#define dsDrawCappedCylinder dsDrawCappedCylinderD
#endif

// some constants

#define NUM 20			// max number of objects
#define DENSITY (5.0)		// density of all objects
#define GPB 3			// maximum number of geometries per body

// dynamics and collision objects

struct MyObject {
  dBodyID body;			// the body
  dGeomID geom[GPB];		// geometries representing this body
};

static int num=0;		// number of objects in simulation
static int nextobj=0;		// next object to recycle if num==NUM
static dWorldID world;
static dSpaceID space;
static MyObject obj[NUM];
static dJointGroupID contactgroup;
static int selected = -1;	// selected object

// this is called by dSpaceCollide when two objects in space are
// potentially colliding.

static void nearCallback (void *data, dGeomID o1, dGeomID o2)
{
  int i;
  // if (o1->body && o2->body) return;

  // exit without doing anything if the two bodies are connected by a joint
  dBodyID b1 = dGeomGetBody(o1);
  dBodyID b2 = dGeomGetBody(o2);
  if (b1 && b2 && dAreConnected (b1,b2)) return;

  dContact contact[3];			// up to 3 contacts per box
  for (i=0; i<3; i++) {
    contact[i].surface.mode = dContactBounce; //dContactMu2;
    contact[i].surface.mu = dInfinity;
    contact[i].surface.mu2 = 0;
    contact[i].surface.bounce = 0.5;
    contact[i].surface.bounce_vel = 0.1;
  }
  if (int numc = dCollide (o1,o2,3,&contact[0].geom,sizeof(dContact))) {
    // dMatrix3 RI;
    // dRSetIdentity (RI);
    // const dReal ss[3] = {0.02,0.02,0.02};
    for (i=0; i<numc; i++) {
      dJointID c = dJointCreateContact (world,contactgroup,contact+i);
      dJointAttach (c,b1,b2);
      // dsDrawBox (contact[i].geom.pos,RI,ss);
    }
  }
}

// start simulation - set viewpoint

static void start()
{
  static float xyz[3] = {2.1640f,-1.3079f,1.7600f};
  static float hpr[3] = {125.5000f,-17.0000f,0.0000f};
  dsSetViewpoint (xyz,hpr);
  printf ("To drop another object, press:\n");
  printf ("   b for box.\n");
  printf ("   s for sphere.\n");
  printf ("   c for cylinder.\n");
  printf ("   x for a composite object.\n");
  printf ("To select an object, press space.\n");
  printf ("To disable the selected object, press d.\n");
  printf ("To enable the selected object, press e.\n");
}

char locase (char c)
{
  if (c >= 'A' && c <= 'Z') return c - ('a'-'A');
  else return c;
}

// called when a key pressed

static void command (int cmd)
{
  int i,j,k;
  dReal sides[3];
  dMass m;

  cmd = locase (cmd);
  if (cmd == 'b' || cmd == 's' || cmd == 'c' || cmd == 'x') {
    if (num < NUM) {
      i = num;
      num++;
    }
    else {
      i = nextobj;
      nextobj++;
      if (nextobj >= num) nextobj = 0;

      // destroy the body and geoms for slot i
      dBodyDestroy (obj[i].body);
      for (k=0; k < GPB; k++) {
	if (obj[i].geom[k]) dGeomDestroy (obj[i].geom[k]);
      }
      memset (&obj[i],0,sizeof(obj[i]));
    }

    obj[i].body = dBodyCreate (world);
    for (k=0; k<3; k++) sides[k] = dRandReal()*0.5+0.1;

    dBodySetPosition (obj[i].body,
		      dRandReal()*2-1,dRandReal()*2-1,dRandReal()+1);
    dMatrix3 R;
    dRFromAxisAndAngle (R,dRandReal()*2.0-1.0,dRandReal()*2.0-1.0,
			dRandReal()*2.0-1.0,dRandReal()*10.0-5.0);
    dBodySetRotation (obj[i].body,R);
    dBodySetData (obj[i].body,(void*) i);

    if (cmd == 'b') {
      dMassSetBox (&m,DENSITY,sides[0],sides[1],sides[2]);
      obj[i].geom[0] = dCreateBox (space,sides[0],sides[1],sides[2]);
    }
    else if (cmd == 'c') {
      sides[0] *= 0.5;
      dMassSetCappedCylinder (&m,DENSITY,3,sides[0],sides[1]);
      obj[i].geom[0] = dCreateCCylinder (space,sides[0],sides[1]);
    }
    else if (cmd == 's') {
      sides[0] *= 0.5;
      dMassSetSphere (&m,DENSITY,sides[0]);
      obj[i].geom[0] = dCreateSphere (space,sides[0]);
    }
    else if (cmd == 'x') {
      dGeomID g2[GPB];		// encapsulated geometries
      dReal dpos[GPB][3];	// delta-positions for encapsulated geometries

      // start accumulating masses for the encapsulated geometries
      dMass m2;
      dMassSetZero (&m);

      // set random delta positions
      for (j=0; j<GPB; j++) {
	for (k=0; k<3; k++) dpos[j][k] = dRandReal()*0.3-0.15;
      }

      for (k=0; k<3; k++) {
	obj[i].geom[k] = dCreateGeomTransform (space);
	dGeomTransformSetCleanup (obj[i].geom[k],1);
	if (k==0) {
	  dReal radius = dRandReal()*0.25+0.05;
	  g2[k] = dCreateSphere (0,radius);
	  dMassSetSphere (&m2,DENSITY,radius);
	}
	else if (k==1) {
	  g2[k] = dCreateBox (0,sides[0],sides[1],sides[2]);
	  dMassSetBox (&m2,DENSITY,sides[0],sides[1],sides[2]);
	}
	else {
	  dReal radius = dRandReal()*0.1+0.05;
	  dReal length = dRandReal()*1.0+0.1;
	  g2[k] = dCreateCCylinder (0,radius,length);
	  dMassSetCappedCylinder (&m2,DENSITY,3,radius,length);
	}
	dGeomTransformSetGeom (obj[i].geom[k],g2[k]);

	// set the transformation (adjust the mass too)
	dGeomSetPosition (g2[k],dpos[k][0],dpos[k][1],dpos[k][2]);
	dMassTranslate (&m2,dpos[k][0],dpos[k][1],dpos[k][2]);
	dMatrix3 Rtx;
	dRFromAxisAndAngle (Rtx,dRandReal()*2.0-1.0,dRandReal()*2.0-1.0,
			    dRandReal()*2.0-1.0,dRandReal()*10.0-5.0);
	dGeomSetRotation (g2[k],Rtx);
	dMassRotate (&m2,Rtx);

	// add to the total mass
	dMassAdd (&m,&m2);
      }

      // move all encapsulated objects so that the center of mass is (0,0,0)
      for (k=0; k<2; k++) {
	dGeomSetPosition (g2[k],
			  dpos[k][0]-m.c[0],
			  dpos[k][1]-m.c[1],
			  dpos[k][2]-m.c[2]);
      }
      dMassTranslate (&m,-m.c[0],-m.c[1],-m.c[2]);
    }

    for (k=0; k < GPB; k++) {
      if (obj[i].geom[k]) dGeomSetBody (obj[i].geom[k],obj[i].body);
    }

    dBodySetMass (obj[i].body,&m);
  }

  if (cmd == ' ') {
    selected++;
    if (selected >= num) selected = 0;
    if (selected < 0) selected = 0;
  }
  else if (cmd == 'd' && selected >= 0 && selected < num) {
    dBodyDisable (obj[selected].body);
  }
  else if (cmd == 'e' && selected >= 0 && selected < num) {
    dBodyEnable (obj[selected].body);
  }
}

// draw a geom

void drawGeom (dGeomID g, const dReal *pos, const dReal *R)
{
  if (!g) return;
  if (!pos) pos = dGeomGetPosition (g);
  if (!R) R = dGeomGetRotation (g);

  int type = dGeomGetClass (g);
  if (type == dBoxClass) {
    dVector3 sides;
    dGeomBoxGetLengths (g,sides);
    dsDrawBox (pos,R,sides);
  }
  else if (type == dSphereClass) {
    dsDrawSphere (pos,R,dGeomSphereGetRadius (g));
  }
  else if (type == dCCylinderClass) {
    dReal radius,length;
    dGeomCCylinderGetParams (g,&radius,&length);
    dsDrawCappedCylinder (pos,R,length,radius);
  }
  else if (type == dGeomTransformClass) {
    dGeomID g2 = dGeomTransformGetGeom (g);
    const dReal *pos2 = dGeomGetPosition (g2);
    const dReal *R2 = dGeomGetRotation (g2);
    dVector3 actual_pos;
    dMatrix3 actual_R;
    dMULTIPLY0_331 (actual_pos,R,pos2);
    actual_pos[0] += pos[0];
    actual_pos[1] += pos[1];
    actual_pos[2] += pos[2];
    dMULTIPLY0_333 (actual_R,R,R2);
    drawGeom (g2,actual_pos,actual_R);
  }
}

// simulation loop

static void simLoop (int pause)
{
  dsSetColor (0,0,2);
  dSpaceCollide (space,0,&nearCallback);
  if (!pause) dWorldStep (world,0.05);

  // remove all contact joints
  dJointGroupEmpty (contactgroup);

  dsSetColor (1,1,0);
  dsSetTexture (DS_WOOD);
  for (int i=0; i<num; i++) {
    int color_changed = 0;
    if (i==selected) {
      dsSetColor (0,0.7,1);
      color_changed = 1;
    }
    else if (! dBodyIsEnabled (obj[i].body)) {
      dsSetColor (1,0,0);
      color_changed = 1;
    }
    for (int j=0; j < GPB; j++) drawGeom (obj[i].geom[j],0,0);
    if (color_changed) dsSetColor (1,1,0);
  }
}

int main (int argc, char **argv)
{
  // setup pointers to drawstuff callback functions
  dsFunctions fn;
  fn.version = DS_VERSION;
  fn.start = &start;
  fn.step = &simLoop;
  fn.command = &command;
  fn.stop = 0;
  fn.path_to_textures = "../../drawstuff/textures";

  // create world

  world = dWorldCreate();
  space = dHashSpaceCreate();
  contactgroup = dJointGroupCreate (0);
  dWorldSetGravity (world,0,0,-0.5);
  dWorldSetCFM (world,1e-5);
  dCreatePlane (space,0,0,1,0);
  memset (obj,0,sizeof(obj));

  // run simulation
  dsSimulationLoop (argc,argv,352,288,&fn);

  dJointGroupDestroy (contactgroup);
  dSpaceDestroy (space);
  dWorldDestroy (world);

  return 0;
}
<commit_msg>added cylinder option (not yet fully integrated). added show-aabb option.<commit_after>/*************************************************************************
 *                                                                       *
 * Open Dynamics Engine, Copyright (C) 2001,2002 Russell L. Smith.       *
 * All rights reserved.  Email: russ@q12.org   Web: www.q12.org          *
 *                                                                       *
 * This library is free software; you can redistribute it and/or         *
 * modify it under the terms of EITHER:                                  *
 *   (1) 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. The text of the GNU Lesser      *
 *       General Public License is included with this library in the     *
 *       file LICENSE.TXT.                                               *
 *   (2) The BSD-style license that is included with this library in     *
 *       the file LICENSE-BSD.TXT.                                       *
 *                                                                       *
 * 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 files    *
 * LICENSE.TXT and LICENSE-BSD.TXT for more details.                     *
 *                                                                       *
 *************************************************************************/

#include <ode/ode.h>
#include <drawstuff/drawstuff.h>

#ifdef MSVC
#pragma warning(disable:4244 4305)  // for VC++, no precision loss complaints
#endif

// select correct drawing functions

#ifdef dDOUBLE
#define dsDrawBox dsDrawBoxD
#define dsDrawSphere dsDrawSphereD
#define dsDrawCylinder dsDrawCylinderD
#define dsDrawCappedCylinder dsDrawCappedCylinderD
#endif

// some constants

#define NUM 20			// max number of objects
#define DENSITY (5.0)		// density of all objects
#define GPB 3			// maximum number of geometries per body

// dynamics and collision objects

struct MyObject {
  dBodyID body;			// the body
  dGeomID geom[GPB];		// geometries representing this body
};

static int num=0;		// number of objects in simulation
static int nextobj=0;		// next object to recycle if num==NUM
static dWorldID world;
static dSpaceID space;
static MyObject obj[NUM];
static dJointGroupID contactgroup;
static int selected = -1;	// selected object
static int show_aabb = 0;	// show geom AABBs?

// this is called by dSpaceCollide when two objects in space are
// potentially colliding.

static void nearCallback (void *data, dGeomID o1, dGeomID o2)
{
  int i;
  // if (o1->body && o2->body) return;

  // exit without doing anything if the two bodies are connected by a joint
  dBodyID b1 = dGeomGetBody(o1);
  dBodyID b2 = dGeomGetBody(o2);
  if (b1 && b2 && dAreConnected (b1,b2)) return;

  dContact contact[3];			// up to 3 contacts per box
  for (i=0; i<3; i++) {
    contact[i].surface.mode = dContactBounce; //dContactMu2;
    contact[i].surface.mu = dInfinity;
    contact[i].surface.mu2 = 0;
    contact[i].surface.bounce = 0.5;
    contact[i].surface.bounce_vel = 0.1;
  }
  if (int numc = dCollide (o1,o2,3,&contact[0].geom,sizeof(dContact))) {
    // dMatrix3 RI;
    // dRSetIdentity (RI);
    // const dReal ss[3] = {0.02,0.02,0.02};
    for (i=0; i<numc; i++) {
      dJointID c = dJointCreateContact (world,contactgroup,contact+i);
      dJointAttach (c,b1,b2);
      // dsDrawBox (contact[i].geom.pos,RI,ss);
    }
  }
}

// start simulation - set viewpoint

static void start()
{
  static float xyz[3] = {2.1640f,-1.3079f,1.7600f};
  static float hpr[3] = {125.5000f,-17.0000f,0.0000f};
  dsSetViewpoint (xyz,hpr);
  printf ("To drop another object, press:\n");
  printf ("   b for box.\n");
  printf ("   s for sphere.\n");
  printf ("   c for cylinder.\n");
  printf ("   x for a composite object.\n");
  printf ("To select an object, press space.\n");
  printf ("To disable the selected object, press d.\n");
  printf ("To enable the selected object, press e.\n");
  printf ("To toggle showing the geom AABBs, press a.\n");
}

char locase (char c)
{
  if (c >= 'A' && c <= 'Z') return c - ('a'-'A');
  else return c;
}

// called when a key pressed

static void command (int cmd)
{
  int i,j,k;
  dReal sides[3];
  dMass m;

  cmd = locase (cmd);
  if (cmd == 'b' || cmd == 's' || cmd == 'c' || cmd == 'x'
      /* || cmd == 'l' */) {
    if (num < NUM) {
      i = num;
      num++;
    }
    else {
      i = nextobj;
      nextobj++;
      if (nextobj >= num) nextobj = 0;

      // destroy the body and geoms for slot i
      dBodyDestroy (obj[i].body);
      for (k=0; k < GPB; k++) {
	if (obj[i].geom[k]) dGeomDestroy (obj[i].geom[k]);
      }
      memset (&obj[i],0,sizeof(obj[i]));
    }

    obj[i].body = dBodyCreate (world);
    for (k=0; k<3; k++) sides[k] = dRandReal()*0.5+0.1;

    dBodySetPosition (obj[i].body,
		      dRandReal()*2-1,dRandReal()*2-1,dRandReal()+1);
    dMatrix3 R;
    dRFromAxisAndAngle (R,dRandReal()*2.0-1.0,dRandReal()*2.0-1.0,
			dRandReal()*2.0-1.0,dRandReal()*10.0-5.0);
    dBodySetRotation (obj[i].body,R);
    dBodySetData (obj[i].body,(void*) i);

    if (cmd == 'b') {
      dMassSetBox (&m,DENSITY,sides[0],sides[1],sides[2]);
      obj[i].geom[0] = dCreateBox (space,sides[0],sides[1],sides[2]);
    }
    else if (cmd == 'c') {
      sides[0] *= 0.5;
      dMassSetCappedCylinder (&m,DENSITY,3,sides[0],sides[1]);
      obj[i].geom[0] = dCreateCCylinder (space,sides[0],sides[1]);
    }
/*
    // cylinder option not yet implemented
    else if (cmd == 'l') {
      sides[1] *= 0.5;
      dMassSetCappedCylinder (&m,DENSITY,3,sides[0],sides[1]);
      obj[i].geom[0] = dCreateCylinder (space,sides[0],sides[1]);
    }
*/
    else if (cmd == 's') {
      sides[0] *= 0.5;
      dMassSetSphere (&m,DENSITY,sides[0]);
      obj[i].geom[0] = dCreateSphere (space,sides[0]);
    }
    else if (cmd == 'x') {
      dGeomID g2[GPB];		// encapsulated geometries
      dReal dpos[GPB][3];	// delta-positions for encapsulated geometries

      // start accumulating masses for the encapsulated geometries
      dMass m2;
      dMassSetZero (&m);

      // set random delta positions
      for (j=0; j<GPB; j++) {
	for (k=0; k<3; k++) dpos[j][k] = dRandReal()*0.3-0.15;
      }

      for (k=0; k<3; k++) {
	obj[i].geom[k] = dCreateGeomTransform (space);
	dGeomTransformSetCleanup (obj[i].geom[k],1);
	if (k==0) {
	  dReal radius = dRandReal()*0.25+0.05;
	  g2[k] = dCreateSphere (0,radius);
	  dMassSetSphere (&m2,DENSITY,radius);
	}
	else if (k==1) {
	  g2[k] = dCreateBox (0,sides[0],sides[1],sides[2]);
	  dMassSetBox (&m2,DENSITY,sides[0],sides[1],sides[2]);
	}
	else {
	  dReal radius = dRandReal()*0.1+0.05;
	  dReal length = dRandReal()*1.0+0.1;
	  g2[k] = dCreateCCylinder (0,radius,length);
	  dMassSetCappedCylinder (&m2,DENSITY,3,radius,length);
	}
	dGeomTransformSetGeom (obj[i].geom[k],g2[k]);

	// set the transformation (adjust the mass too)
	dGeomSetPosition (g2[k],dpos[k][0],dpos[k][1],dpos[k][2]);
	dMassTranslate (&m2,dpos[k][0],dpos[k][1],dpos[k][2]);
	dMatrix3 Rtx;
	dRFromAxisAndAngle (Rtx,dRandReal()*2.0-1.0,dRandReal()*2.0-1.0,
			    dRandReal()*2.0-1.0,dRandReal()*10.0-5.0);
	dGeomSetRotation (g2[k],Rtx);
	dMassRotate (&m2,Rtx);

	// add to the total mass
	dMassAdd (&m,&m2);
      }

      // move all encapsulated objects so that the center of mass is (0,0,0)
      for (k=0; k<2; k++) {
	dGeomSetPosition (g2[k],
			  dpos[k][0]-m.c[0],
			  dpos[k][1]-m.c[1],
			  dpos[k][2]-m.c[2]);
      }
      dMassTranslate (&m,-m.c[0],-m.c[1],-m.c[2]);
    }

    for (k=0; k < GPB; k++) {
      if (obj[i].geom[k]) dGeomSetBody (obj[i].geom[k],obj[i].body);
    }

    dBodySetMass (obj[i].body,&m);
  }

  if (cmd == ' ') {
    selected++;
    if (selected >= num) selected = 0;
    if (selected < 0) selected = 0;
  }
  else if (cmd == 'd' && selected >= 0 && selected < num) {
    dBodyDisable (obj[selected].body);
  }
  else if (cmd == 'e' && selected >= 0 && selected < num) {
    dBodyEnable (obj[selected].body);
  }
  else if (cmd == 'a') {
    show_aabb ^= 1;
  }
}

// draw a geom

void drawGeom (dGeomID g, const dReal *pos, const dReal *R, int show_aabb)
{
  if (!g) return;
  if (!pos) pos = dGeomGetPosition (g);
  if (!R) R = dGeomGetRotation (g);

  int type = dGeomGetClass (g);
  if (type == dBoxClass) {
    dVector3 sides;
    dGeomBoxGetLengths (g,sides);
    dsDrawBox (pos,R,sides);
  }
  else if (type == dSphereClass) {
    dsDrawSphere (pos,R,dGeomSphereGetRadius (g));
  }
  else if (type == dCCylinderClass) {
    dReal radius,length;
    dGeomCCylinderGetParams (g,&radius,&length);
    dsDrawCappedCylinder (pos,R,length,radius);
  }
/*
  // cylinder option not yet implemented
  else if (type == dCylinderClass) {
    dReal radius,length;
    dGeomCylinderGetParams (g,&radius,&length);
    dsDrawCylinder (pos,R,length,radius);
  }
*/
  else if (type == dGeomTransformClass) {
    dGeomID g2 = dGeomTransformGetGeom (g);
    const dReal *pos2 = dGeomGetPosition (g2);
    const dReal *R2 = dGeomGetRotation (g2);
    dVector3 actual_pos;
    dMatrix3 actual_R;
    dMULTIPLY0_331 (actual_pos,R,pos2);
    actual_pos[0] += pos[0];
    actual_pos[1] += pos[1];
    actual_pos[2] += pos[2];
    dMULTIPLY0_333 (actual_R,R,R2);
    drawGeom (g2,actual_pos,actual_R,0);
  }

  if (show_aabb) {
    // draw the bounding box for this geom
    dReal aabb[6];
    dGeomGetAABB (g,aabb);
    dVector3 bbpos;
    for (int i=0; i<3; i++) bbpos[i] = 0.5*(aabb[i*2] + aabb[i*2+1]);
    dVector3 bbsides;
    for (int i=0; i<3; i++) bbsides[i] = aabb[i*2+1] - aabb[i*2];
    dMatrix3 RI;
    dRSetIdentity (RI);
    dsSetColorAlpha (1,0,0,0.5);
    dsDrawBox (bbpos,RI,bbsides);
  }
}

// simulation loop

static void simLoop (int pause)
{
  dsSetColor (0,0,2);
  dSpaceCollide (space,0,&nearCallback);
  if (!pause) dWorldStep (world,0.05);

  // remove all contact joints
  dJointGroupEmpty (contactgroup);

  dsSetColor (1,1,0);
  dsSetTexture (DS_WOOD);
  for (int i=0; i<num; i++) {
    for (int j=0; j < GPB; j++) {
      if (i==selected) {
	dsSetColor (0,0.7,1);
      }
      else if (! dBodyIsEnabled (obj[i].body)) {
	dsSetColor (1,0,0);
      }
      else {
	dsSetColor (1,1,0);
      }
      drawGeom (obj[i].geom[j],0,0,show_aabb);
    }
  }
}

int main (int argc, char **argv)
{
  // setup pointers to drawstuff callback functions
  dsFunctions fn;
  fn.version = DS_VERSION;
  fn.start = &start;
  fn.step = &simLoop;
  fn.command = &command;
  fn.stop = 0;
  fn.path_to_textures = "../../drawstuff/textures";

  // create world

  world = dWorldCreate();
  space = dHashSpaceCreate (0);
  contactgroup = dJointGroupCreate (0);
  dWorldSetGravity (world,0,0,-0.5);
  dWorldSetCFM (world,1e-5);
  dCreatePlane (space,0,0,1,0);
  memset (obj,0,sizeof(obj));

  // run simulation
  dsSimulationLoop (argc,argv,352,288,&fn);

  dJointGroupDestroy (contactgroup);
  dSpaceDestroy (space);
  dWorldDestroy (world);

  return 0;
}
<|endoftext|>


<commit_before>//
//  Enterprise.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 10/06/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#include "Enterprise.hpp"

#include "Keyboard.hpp"
#include "Nick.hpp"

#include "../MachineTypes.hpp"

#include "../../Processors/Z80/Z80.hpp"

#include "../../Analyser/Static/Enterprise/Target.hpp"

#include "../../ClockReceiver/JustInTime.hpp"

namespace Enterprise {

/*
	Notes to self on timing:

	Nick divides each line into 57 windows; each window lasts 16 cycles and dedicates the
	first 10 of those to VRAM accesses, leaving the final six for a Z80 video RAM access
	if one has been requested.

	The Z80 has a separate, asynchronous 4Mhz clock. That's that.

	The documentation is also very forward in emphasising that Nick generates phaselocked
	(i.e. in-phase) PAL video.

	So: 57*16 = 912 cycles/line.

	A standard PAL line lasts 64µs and during that time outputs 283.7516 colour cycles.

	I shall _guess_ that the Enterprise stretches each line to 284 colour cycles rather than
	reducing it to 283.

	Therefore 912 cycles occurs in 284/283.7516 * 64 µs, which would appear to give an ideal
	clock rate of around:

		14,237,536.27 Hz.

	Given that there's always some leeway in a receiver, I'm modelling that as 14,237,536 cycles,
	which means that Nick runs 444923/125000 times as fast as the Z80. Which is around 3.56 times
	as fast.

	If that's true then the 6-cycle window is around 1.69 Z80 cycles long. Given that the Z80
	clock in an Enterprise can be stopped in half-cycle increments only, the Z80 can only be
	guaranteed to have around a 1.19 cycle minimum for its actual access. I'm therefore further
	postulating that the clock stoppage takes place so as to align the final cycle of a relevant
	access over the available window.

*/

class ConcreteMachine:
	public CPU::Z80::BusHandler,
	public Machine,
	public MachineTypes::MappedKeyboardMachine,
	public MachineTypes::ScanProducer,
	public MachineTypes::TimedMachine {
	public:
		ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
			z80_(*this),
			nick_(ram_.end() - 65536) {
			// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
			set_clock_rate(4'000'000);

			constexpr ROM::Name exos_name = ROM::Name::EnterpriseEXOS;
			const auto request = ROM::Request(exos_name);
			auto roms = rom_fetcher(request);
			if(!request.validate(roms)) {
				throw ROMMachine::Error::MissingROMs;
			}

			const auto &exos = roms.find(exos_name)->second;
			exos_.fill(0xff);
			memcpy(exos_.data(), exos.data(), std::min(exos_.size(), exos.size()));

			// Take a reasonable guess at the initial memory configuration:
			// put EXOS into the first bank since this is a Z80 and therefore
			// starts from address 0; the third instruction in EXOS is a jump
			// to $c02e so it's reasonable to assume EXOS is in the highest bank
			// too, and it appears to act correctly if it's the first 16kb that's
			// in the highest bank. From there I guess: all banks are initialised
			// to 0.
			page<0>(0x00);
			page<1>(0x00);
			page<2>(0x00);
			page<3>(0x00);
		}

		// MARK: - Z80::BusHandler.
		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
			using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
			const uint16_t address = cycle.address ? *cycle.address : 0x0000;

			// TODO: possibly apply an access penalty.

			if(nick_ += cycle.length) {
				const auto nick = nick_.last_valid();
				const bool nick_interrupt_line = nick->get_interrupt_line();
				if(nick_interrupt_line && !previous_nick_interrupt_line_) {
					// TODO: apply interrupt.
				}
				previous_nick_interrupt_line_ = nick_interrupt_line;
			}

			switch(cycle.operation) {
				default: break;

				case CPU::Z80::PartialMachineCycle::Input:
					switch(address & 0xff) {
						default:
							printf("Unhandled input: %04x\n", address);
							assert(false);
						break;

						case 0xb0:	*cycle.value = pages_[0];	break;
						case 0xb1:	*cycle.value = pages_[1];	break;
						case 0xb2:	*cycle.value = pages_[2];	break;
						case 0xb3:	*cycle.value = pages_[3];	break;

						case 0xb4:
							printf("TODO: interrupt enable/reset read\n");
							*cycle.value = 0xff;
						break;
						case 0xb5:
							if(active_key_line_ < key_lines_.size()) {
								*cycle.value = key_lines_[active_key_line_];
							} else {
								*cycle.value = 0xff;
							}
						break;
					}
				break;

				case CPU::Z80::PartialMachineCycle::Output:
					switch(address & 0xff) {
						default:
							printf("Unhandled output: %04x\n", address);
							assert(false);
						break;

						case 0x80:	case 0x81:	case 0x82:	case 0x83:
						case 0x84:	case 0x85:	case 0x86:	case 0x87:
						case 0x88:	case 0x89:	case 0x8a:	case 0x8b:
						case 0x8c:	case 0x8d:	case 0x8e:	case 0x8f:
							nick_->write(address, *cycle.value);
						break;

						case 0xb0:	page<0>(*cycle.value);	break;
						case 0xb1:	page<1>(*cycle.value);	break;
						case 0xb2:	page<2>(*cycle.value);	break;
						case 0xb3:	page<3>(*cycle.value);	break;

						case 0xa0:	case 0xa1:	case 0xa2:	case 0xa3:
						case 0xa4:	case 0xa5:	case 0xa6:	case 0xa7:
						case 0xa8:	case 0xa9:	case 0xaa:	case 0xab:
						case 0xac:	case 0xad:	case 0xae:	case 0xaf:
							printf("TODO: audio adjust %04x <- %02x\n", address, *cycle.value);
						break;

						case 0xb4:
							printf("TODO: interrupt enable/reset write %02x\n", *cycle.value);
						break;
						case 0xb5:
							active_key_line_ = *cycle.value & 0xf;
							// TODO: printer strobe, type sound, REM switches.
						break;
						case 0xb6:
							printf("TODO: printer output %02x\n", *cycle.value);
						break;
						case 0xbf:
							printf("TODO: Dave sysconfig %02x\n", *cycle.value);
						break;
					}
				break;

				case CPU::Z80::PartialMachineCycle::Read:
				case CPU::Z80::PartialMachineCycle::ReadOpcode:
					if(read_pointers_[address >> 14]) {
						*cycle.value = read_pointers_[address >> 14][address];
					} else {
						*cycle.value = 0xff;
					}
				break;

				case CPU::Z80::PartialMachineCycle::Write:
					if(write_pointers_[address >> 14]) {
						write_pointers_[address >> 14][address] = *cycle.value;
					}
				break;
			}

			return HalfCycles(0);
		}

		void flush() {
			nick_.flush();
		}

	private:
		// MARK: - Memory layout
		std::array<uint8_t, 256 * 1024> ram_;
		std::array<uint8_t, 64 * 1024> exos_;
		const uint8_t min_ram_slot_ = 0xff - 16 + 1;

		const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
		uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
		uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};

		template <size_t slot> void page(uint8_t offset) {
			pages_[slot] = offset;

			if(offset < exos_.size() / 0x4000) {
				page<slot>(&exos_[offset * 0x4000], nullptr);
				return;
			}

			// Of whatever size of RAM I've declared above, use only the final portion.
			// This correlated with Nick always having been handed the final 64kb and,
			// at least while the RAM is the first thing declared above, does a little
			// to benefit data locality. Albeit not in a useful sense.
			if(offset >= min_ram_slot_) {
				const auto ram_floor = 4194304 - ram_.size();
				const size_t address = offset * 0x4000 - ram_floor;
				page<slot>(&ram_[address], &ram_[address]);
				return;
			}

			page<slot>(nullptr, nullptr);
		}

		template <size_t slot> void page(const uint8_t *read, uint8_t *write) {
			read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
			write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
		}

		// MARK: - ScanProducer
		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
			nick_.last_valid()->set_scan_target(scan_target);
		}

		Outputs::Display::ScanStatus get_scaled_scan_status() const override {
			return nick_.last_valid()->get_scaled_scan_status();
		}

		// MARK: - TimedMachine
		void run_for(const Cycles cycles) override {
			z80_.run_for(cycles);
		}

		// MARK: - KeyboardMachine
		Enterprise::KeyboardMapper keyboard_mapper_;
		KeyboardMapper *get_keyboard_mapper() final {
			return &keyboard_mapper_;
		}

		uint8_t active_key_line_ = 0;
		std::array<uint8_t, 10> key_lines_;
		void set_key_state(uint16_t key, bool is_pressed) final {
			if(is_pressed) {
				key_lines_[key >> 8] &= ~uint8_t(key);
			} else {
				key_lines_[key >> 8] |= uint8_t(key);
			}
		}

		void clear_all_keys() final {
			key_lines_.fill(0xff);
		}

		// MARK: - Chips.
		CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
		JustInTimeActor<Nick, HalfCycles, 444923, 125000> nick_;
		bool previous_nick_interrupt_line_ = false;
		// Cf. timing guesses above.
};

}

using namespace Enterprise;

Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
	using Target = Analyser::Static::Enterprise::Target;
	const Target *const enterprise_target = dynamic_cast<const Target *>(target);

	return new Enterprise::ConcreteMachine(*enterprise_target, rom_fetcher);
}

Machine::~Machine() {}
<commit_msg>Attempts to implement interrupt latches and clears.<commit_after>//
//  Enterprise.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 10/06/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#include "Enterprise.hpp"

#include "Keyboard.hpp"
#include "Nick.hpp"

#include "../MachineTypes.hpp"

#include "../../Processors/Z80/Z80.hpp"

#include "../../Analyser/Static/Enterprise/Target.hpp"

#include "../../ClockReceiver/JustInTime.hpp"

namespace Enterprise {

/*
	Notes to self on timing:

	Nick divides each line into 57 windows; each window lasts 16 cycles and dedicates the
	first 10 of those to VRAM accesses, leaving the final six for a Z80 video RAM access
	if one has been requested.

	The Z80 has a separate, asynchronous 4Mhz clock. That's that.

	The documentation is also very forward in emphasising that Nick generates phaselocked
	(i.e. in-phase) PAL video.

	So: 57*16 = 912 cycles/line.

	A standard PAL line lasts 64µs and during that time outputs 283.7516 colour cycles.

	I shall _guess_ that the Enterprise stretches each line to 284 colour cycles rather than
	reducing it to 283.

	Therefore 912 cycles occurs in 284/283.7516 * 64 µs, which would appear to give an ideal
	clock rate of around:

		14,237,536.27 Hz.

	Given that there's always some leeway in a receiver, I'm modelling that as 14,237,536 cycles,
	which means that Nick runs 444923/125000 times as fast as the Z80. Which is around 3.56 times
	as fast.

	If that's true then the 6-cycle window is around 1.69 Z80 cycles long. Given that the Z80
	clock in an Enterprise can be stopped in half-cycle increments only, the Z80 can only be
	guaranteed to have around a 1.19 cycle minimum for its actual access. I'm therefore further
	postulating that the clock stoppage takes place so as to align the final cycle of a relevant
	access over the available window.

*/

class ConcreteMachine:
	public CPU::Z80::BusHandler,
	public Machine,
	public MachineTypes::MappedKeyboardMachine,
	public MachineTypes::ScanProducer,
	public MachineTypes::TimedMachine {
	public:
		ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
			z80_(*this),
			nick_(ram_.end() - 65536) {
			// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
			set_clock_rate(4'000'000);

			constexpr ROM::Name exos_name = ROM::Name::EnterpriseEXOS;
			const auto request = ROM::Request(exos_name);
			auto roms = rom_fetcher(request);
			if(!request.validate(roms)) {
				throw ROMMachine::Error::MissingROMs;
			}

			const auto &exos = roms.find(exos_name)->second;
			exos_.fill(0xff);
			memcpy(exos_.data(), exos.data(), std::min(exos_.size(), exos.size()));

			// Take a reasonable guess at the initial memory configuration:
			// put EXOS into the first bank since this is a Z80 and therefore
			// starts from address 0; the third instruction in EXOS is a jump
			// to $c02e so it's reasonable to assume EXOS is in the highest bank
			// too, and it appears to act correctly if it's the first 16kb that's
			// in the highest bank. From there I guess: all banks are initialised
			// to 0.
			page<0>(0x00);
			page<1>(0x00);
			page<2>(0x00);
			page<3>(0x00);
		}

		// MARK: - Z80::BusHandler.
		forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
			using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
			const uint16_t address = cycle.address ? *cycle.address : 0x0000;

			// TODO: possibly apply an access penalty.

			if(nick_ += cycle.length) {
				const auto nick = nick_.last_valid();
				const bool nick_interrupt_line = nick->get_interrupt_line();
				if(nick_interrupt_line && !previous_nick_interrupt_line_) {
					set_interrupt(Interrupt::Nick, nick_.last_sequence_point_overrun());
				}
				previous_nick_interrupt_line_ = nick_interrupt_line;
			}

			switch(cycle.operation) {
				default: break;

				case CPU::Z80::PartialMachineCycle::Input:
					switch(address & 0xff) {
						default:
							printf("Unhandled input: %04x\n", address);
							assert(false);
						break;

						case 0xb0:	*cycle.value = pages_[0];	break;
						case 0xb1:	*cycle.value = pages_[1];	break;
						case 0xb2:	*cycle.value = pages_[2];	break;
						case 0xb3:	*cycle.value = pages_[3];	break;

						case 0xb4:
							*cycle.value = interrupt_mask_ | interrupt_state_;
						break;
						case 0xb5:
							if(active_key_line_ < key_lines_.size()) {
								*cycle.value = key_lines_[active_key_line_];
							} else {
								*cycle.value = 0xff;
							}
						break;
					}
				break;

				case CPU::Z80::PartialMachineCycle::Output:
					switch(address & 0xff) {
						default:
							printf("Unhandled output: %04x\n", address);
							assert(false);
						break;

						case 0x80:	case 0x81:	case 0x82:	case 0x83:
						case 0x84:	case 0x85:	case 0x86:	case 0x87:
						case 0x88:	case 0x89:	case 0x8a:	case 0x8b:
						case 0x8c:	case 0x8d:	case 0x8e:	case 0x8f:
							nick_->write(address, *cycle.value);
						break;

						case 0xb0:	page<0>(*cycle.value);	break;
						case 0xb1:	page<1>(*cycle.value);	break;
						case 0xb2:	page<2>(*cycle.value);	break;
						case 0xb3:	page<3>(*cycle.value);	break;

						case 0xa0:	case 0xa1:	case 0xa2:	case 0xa3:
						case 0xa4:	case 0xa5:	case 0xa6:	case 0xa7:
						case 0xa8:	case 0xa9:	case 0xaa:	case 0xab:
						case 0xac:	case 0xad:	case 0xae:	case 0xaf:
							printf("TODO: audio adjust %04x <- %02x\n", address, *cycle.value);
						break;

						case 0xb4:
							interrupt_mask_ = *cycle.value & 0x55;
							interrupt_state_ &= ~*cycle.value;
							update_interrupts();
						break;
						case 0xb5:
							active_key_line_ = *cycle.value & 0xf;
							// TODO: printer strobe, type sound, REM switches.
						break;
						case 0xb6:
							printf("TODO: printer output %02x\n", *cycle.value);
						break;
						case 0xbf:
							printf("TODO: Dave sysconfig %02x\n", *cycle.value);
						break;
					}
				break;

				case CPU::Z80::PartialMachineCycle::Read:
				case CPU::Z80::PartialMachineCycle::ReadOpcode:
					if(read_pointers_[address >> 14]) {
						*cycle.value = read_pointers_[address >> 14][address];
					} else {
						*cycle.value = 0xff;
					}
				break;

				case CPU::Z80::PartialMachineCycle::Write:
					if(write_pointers_[address >> 14]) {
						write_pointers_[address >> 14][address] = *cycle.value;
					}
				break;
			}

			return HalfCycles(0);
		}

		void flush() {
			nick_.flush();
		}

	private:
		// MARK: - Memory layout
		std::array<uint8_t, 256 * 1024> ram_;
		std::array<uint8_t, 64 * 1024> exos_;
		const uint8_t min_ram_slot_ = 0xff - 16 + 1;

		const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
		uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
		uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};

		template <size_t slot> void page(uint8_t offset) {
			pages_[slot] = offset;

			if(offset < exos_.size() / 0x4000) {
				page<slot>(&exos_[offset * 0x4000], nullptr);
				return;
			}

			// Of whatever size of RAM I've declared above, use only the final portion.
			// This correlated with Nick always having been handed the final 64kb and,
			// at least while the RAM is the first thing declared above, does a little
			// to benefit data locality. Albeit not in a useful sense.
			if(offset >= min_ram_slot_) {
				const auto ram_floor = 4194304 - ram_.size();
				const size_t address = offset * 0x4000 - ram_floor;
				page<slot>(&ram_[address], &ram_[address]);
				return;
			}

			page<slot>(nullptr, nullptr);
		}

		template <size_t slot> void page(const uint8_t *read, uint8_t *write) {
			read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
			write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
		}

		// MARK: - ScanProducer
		void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
			nick_.last_valid()->set_scan_target(scan_target);
		}

		Outputs::Display::ScanStatus get_scaled_scan_status() const override {
			return nick_.last_valid()->get_scaled_scan_status();
		}

		// MARK: - TimedMachine
		void run_for(const Cycles cycles) override {
			z80_.run_for(cycles);
		}

		// MARK: - KeyboardMachine
		Enterprise::KeyboardMapper keyboard_mapper_;
		KeyboardMapper *get_keyboard_mapper() final {
			return &keyboard_mapper_;
		}

		uint8_t active_key_line_ = 0;
		std::array<uint8_t, 10> key_lines_;
		void set_key_state(uint16_t key, bool is_pressed) final {
			if(is_pressed) {
				key_lines_[key >> 8] &= ~uint8_t(key);
			} else {
				key_lines_[key >> 8] |= uint8_t(key);
			}
		}

		void clear_all_keys() final {
			key_lines_.fill(0xff);
		}

		// MARK: - Interrupts

		// TODO: include an error.

		enum class Interrupt: uint8_t {
			Nick = 0x20
		};

		uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
		void set_interrupt(Interrupt mask, HalfCycles offset = HalfCycles(0)) {
			interrupt_state_ |= uint8_t(mask);
			update_interrupts(offset);
		}
		void update_interrupts(HalfCycles offset = HalfCycles(0)) {
			z80_.set_interrupt_line((interrupt_state_ >> 1) & interrupt_mask_, offset);
		}

		// MARK: - Chips.
		CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
		JustInTimeActor<Nick, HalfCycles, 444923, 125000> nick_;
		bool previous_nick_interrupt_line_ = false;
		// Cf. timing guesses above.
};

}

using namespace Enterprise;

Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
	using Target = Analyser::Static::Enterprise::Target;
	const Target *const enterprise_target = dynamic_cast<const Target *>(target);

	return new Enterprise::ConcreteMachine(*enterprise_target, rom_fetcher);
}

Machine::~Machine() {}
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#ifdef WIN32

#include "MiNiFiWindowsService.h"

#include <Windows.h>
#include <Strsafe.h>
#include <tuple>
#include <tlhelp32.h>

#include "MainHelper.h"
#include "core/FlowConfiguration.h"

#ifdef LOG_INFO
  #undef LOG_INFO
#endif
#ifdef LOG_ERROR
  #undef LOG_ERROR
#endif
#ifdef LOG_LASTERROR
  #undef LOG_LASTERROR
#endif

#ifdef DEBUG_SERVICE
  #define LOG_INFO(...)       OutputDebug(__VA_ARGS__)
  #define LOG_ERROR(...)      OutputDebug(__VA_ARGS__)
  #define LOG_LASTERROR(str)  OutputDebug(str " lastError %x", GetLastError())
#else
  #define LOG_INFO(...)       Log()->log_info(__VA_ARGS__)
  #define LOG_ERROR(...)      Log()->log_error(__VA_ARGS__)
  #define LOG_LASTERROR(str)  Log()->log_error(str " lastError %x", GetLastError())
#endif

#undef DEBUG_SERVICE

// Implemented in MiNiFiMain.cpp
void SignalExitProcess();

static const char* const SERVICE_TERMINATION_EVENT_NAME = "Global\\MiNiFiServiceTermination";

static void OutputDebug(const char* format, ...) {
  va_list args;
  va_start(args, format);

  char buf[256];
  sprintf_s(buf, _countof(buf), "%s: %s", SERVICE_NAME, format);

  char out[1024];
  StringCbVPrintfA(out, sizeof(out), buf, args);

  OutputDebugStringA(out);

  va_end(args);
};

void RunAsServiceIfNeeded() {
  static const int WAIT_TIME_EXE_TERMINATION = 5000;
  static const int WAIT_TIME_EXE_RESTART = 60000;

  static SERVICE_STATUS s_serviceStatus;
  static SERVICE_STATUS_HANDLE s_statusHandle;
  static HANDLE s_hProcess;
  static HANDLE s_hEvent;

  static auto Log = []() {
    static std::shared_ptr<logging::Logger> s_logger = logging::LoggerConfiguration::getConfiguration().getLogger("service");
    return s_logger;
  };

  SERVICE_TABLE_ENTRY serviceTable[] = {
    {
      SERVICE_NAME,
      [](DWORD argc, LPTSTR *argv) {
        setSyslogLogger();

        LOG_INFO("ServiceCtrlDispatcher");

        s_hEvent = CreateEvent(0, TRUE, FALSE, SERVICE_TERMINATION_EVENT_NAME);
        if (!s_hEvent) {
          LOG_LASTERROR("!CreateEvent");
          return;
        }

        s_statusHandle = RegisterServiceCtrlHandler(
          SERVICE_NAME,
          [](DWORD ctrlCode) {
            LOG_INFO("ServiceCtrlHandler ctrlCode %d", ctrlCode);

            if (SERVICE_CONTROL_STOP == ctrlCode) {
              LOG_INFO("ServiceCtrlHandler ctrlCode = SERVICE_CONTROL_STOP");

              // Set service status SERVICE_STOP_PENDING.
              s_serviceStatus.dwControlsAccepted = 0;
              s_serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
              s_serviceStatus.dwWin32ExitCode = 0;

              if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
                LOG_LASTERROR("!SetServiceStatus SERVICE_STOP_PENDING");
              }

              bool exeTerminated = false;

              SetEvent(s_hEvent);

              LOG_INFO("Wait for exe termination");
              switch (auto res = WaitForSingleObject(s_hProcess, WAIT_TIME_EXE_TERMINATION)) {
                case WAIT_OBJECT_0:
                  LOG_INFO("Exe terminated");
                  exeTerminated = true;
                  break;

                case WAIT_TIMEOUT:
                  LOG_ERROR("WaitForSingleObject timeout %d", WAIT_TIME_EXE_TERMINATION);
                  break;

                default:
                  LOG_ERROR("!WaitForSingleObject return %d", res);
              }

              if (!exeTerminated) {
                LOG_INFO("TerminateProcess");
                if (TerminateProcess(s_hProcess, 0)) {
                  s_serviceStatus.dwControlsAccepted = 0;
                  s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
                  s_serviceStatus.dwWin32ExitCode = 0;

                  if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
                    LOG_LASTERROR("!SetServiceStatus SERVICE_STOPPED");
                  }
                } else {
                  LOG_LASTERROR("!TerminateProcess");
                }
              }
            }
          }
        );

        if (!s_statusHandle) {
          LOG_LASTERROR("!RegisterServiceCtrlHandler");
          return;
        }

        // Set service status SERVICE_START_PENDING.
        ZeroMemory(&s_serviceStatus, sizeof(s_serviceStatus));
        s_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        s_serviceStatus.dwControlsAccepted = 0;
        s_serviceStatus.dwCurrentState = SERVICE_START_PENDING;
        s_serviceStatus.dwWin32ExitCode = 0;
        s_serviceStatus.dwServiceSpecificExitCode = 0;

        if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
          LOG_LASTERROR("!SetServiceStatus SERVICE_START_PENDING");
          return;
        }

        // Set service status SERVICE_RUNNING.
        s_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
        s_serviceStatus.dwCurrentState = SERVICE_RUNNING;
        s_serviceStatus.dwWin32ExitCode = 0;

        if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
          LOG_LASTERROR("!SetServiceStatus SERVICE_RUNNING");

          // Set service status SERVICE_START_PENDING.
          s_serviceStatus.dwControlsAccepted = 0;
          s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
          s_serviceStatus.dwWin32ExitCode = GetLastError();

          if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
            LOG_LASTERROR("!SetServiceStatus SERVICE_STOPPED");
          }

          return;
        }

        char filePath[MAX_PATH];
        if (!GetModuleFileName(0, filePath, _countof(filePath))) {
          LOG_LASTERROR("!GetModuleFileName");
          return;
        }

        do {
          LOG_INFO("Start exe path %s", filePath);

          STARTUPINFO startupInfo{};
          startupInfo.cb = sizeof(startupInfo);

          PROCESS_INFORMATION processInformation{};

          if (!CreateProcess(filePath, 0, 0, 0, 0, FALSE, 0, 0, &startupInfo, &processInformation)) {
            LOG_LASTERROR("!CreateProcess");
            return;
          }

          s_hProcess = processInformation.hProcess;

          LOG_INFO("%s started", filePath);

          auto res = WaitForSingleObject(processInformation.hProcess, INFINITE);
          CloseHandle(processInformation.hProcess);
          CloseHandle(processInformation.hThread);

          if (WAIT_FAILED == res) {
            LOG_LASTERROR("!WaitForSingleObject hProcess");
          } else if (WAIT_OBJECT_0 != res) {
            LOG_ERROR("!WaitForSingleObject hProcess return %d", res);
          }

          LOG_INFO("Sleep %d sec before restarting exe", WAIT_TIME_EXE_RESTART/1000);
          res = WaitForSingleObject(s_hEvent, WAIT_TIME_EXE_RESTART);

          if (WAIT_OBJECT_0 == res) {
            LOG_INFO("Service was stopped, exe won't be restarted");
            break;
          }

          if (WAIT_FAILED == res) {
            LOG_LASTERROR("!WaitForSingleObject s_hEvent");
          } if (WAIT_TIMEOUT != res) {
            LOG_ERROR("!WaitForSingleObject s_hEvent return %d", res);
          }
        } while (true);

        s_serviceStatus.dwControlsAccepted = 0;
        s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
        s_serviceStatus.dwWin32ExitCode = 0;

        if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
          LOG_LASTERROR("!SetServiceStatus SERVICE_STOPPED");
        }
      }
    },
    {0, 0}
  };

  if (!StartServiceCtrlDispatcher(serviceTable)) {
    if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == GetLastError()) {
      // Run this exe as console.
      return;
    }

    LOG_LASTERROR("!StartServiceCtrlDispatcher");

    ExitProcess(1);
  }

  LOG_INFO("Service exit");

  ExitProcess(0);
}

HANDLE GetTerminationEventHandle(bool* isStartedByService) {
  *isStartedByService = true;
  HANDLE hEvent = CreateEvent(0, TRUE, FALSE, SERVICE_TERMINATION_EVENT_NAME);
  if (!hEvent) {
    return nullptr;
  }

  if (GetLastError() != ERROR_ALREADY_EXISTS) {
    *isStartedByService = false;
  }

  return hEvent;
}

bool CreateServiceTerminationThread(std::shared_ptr<logging::Logger> logger, HANDLE terminationEventHandle) {
  // Get hService and monitor it - if service is terminated, then terminate current exe, otherwise the exe becomes unmanageable when service is restarted.
  auto hService = [&logger]() -> HANDLE {
    auto hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (INVALID_HANDLE_VALUE == hSnapShot) {
      logger->log_error("!CreateToolhelp32Snapshot lastError %x", GetLastError());
      return 0;
    }

    auto getProcessInfo = [&logger, &hSnapShot](DWORD processId, DWORD& parentProcessId, std::string& parentProcessName) {
      parentProcessId = 0;
      parentProcessName.clear();

      PROCESSENTRY32 procentry{};
      procentry.dwSize = sizeof(procentry);

      if (!Process32First(hSnapShot, &procentry)) {
        logger->log_error("!Process32First lastError %x", GetLastError());
        return;
      }

      do {
        if (processId == procentry.th32ProcessID) {
          parentProcessId = procentry.th32ParentProcessID;
          parentProcessName = procentry.szExeFile;
          return;
        }
      } while (Process32Next(hSnapShot, &procentry));
    };

    // Find current process info, which contains parentProcessId.
    DWORD parentProcessId{};
    std::string parentProcessName;
    getProcessInfo(GetCurrentProcessId(), parentProcessId, parentProcessName);

    // Find parent process info (the service which started current process), which contains service name.
    DWORD parentParentProcessId{};
    getProcessInfo(parentProcessId, parentParentProcessId, parentProcessName);

    CloseHandle(hSnapShot);

    // Just in case check that service name == current process name.
    char filePath[MAX_PATH];
    if (!GetModuleFileName(0, filePath, _countof(filePath))) {
      logger->log_error("!GetModuleFileName lastError %x", GetLastError());
      return 0;
    }

    const auto pSlash = strrchr(filePath, '\\');
    if (!pSlash) {
      logger->log_error("Invalid filePath %s", filePath);
      return 0;
    }
    const std::string fileName = pSlash + 1;

    if (_stricmp(fileName.c_str(), parentProcessName.c_str())) {
      logger->log_error("Parent process %s != current process %s", parentProcessName.c_str(), fileName.c_str());
      return 0;
    }

    const auto hParentProcess = OpenProcess(SYNCHRONIZE, FALSE, parentProcessId);
    if (!hParentProcess) {
      logger->log_error("!OpenProcess lastError %x", GetLastError());
      return 0;
    }

    return hParentProcess;
  }();
  if (!hService)
    return false;

  using ThreadInfo = std::tuple<std::shared_ptr<logging::Logger>, HANDLE, HANDLE>;
  auto pThreadInfo = new ThreadInfo(logger, terminationEventHandle, hService);

  HANDLE hThread = (HANDLE)_beginthreadex(
    0, 0,
    [](void* pPar) {
      const auto pThreadInfo = static_cast<ThreadInfo*>(pPar);
      const auto logger = std::get<0>(*pThreadInfo);
      const auto terminationEventHandle = std::get<1>(*pThreadInfo);
      const auto hService = std::get<2>(*pThreadInfo);
      delete pThreadInfo;

      HANDLE arHandle[] = { terminationEventHandle, hService };
      switch (auto res = WaitForMultipleObjects(_countof(arHandle), arHandle, FALSE, INFINITE)) {
        case WAIT_FAILED:
          logger->log_error("!WaitForSingleObject lastError %x", GetLastError());
        break;

        case WAIT_OBJECT_0:
          logger->log_info("Service event received");
        break;

        case WAIT_OBJECT_0 + 1:
          logger->log_info("Service is terminated");
        break;

        default:
          logger->log_info("WaitForMultipleObjects return %d", res);
      }

      SignalExitProcess();

      return 0U;
    },
    pThreadInfo,
    0, 0);
  if (!hThread) {
    logger->log_error("!_beginthreadex lastError %x", GetLastError());

    delete pThreadInfo;

    return false;
  }

  return true;
}

#endif
<commit_msg>MINIFICPP-1362 clean up windows service logging<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#ifdef WIN32

#include "MiNiFiWindowsService.h"

#include <Windows.h>
#include <Strsafe.h>
#include <tuple>
#include <tlhelp32.h>

#include "MainHelper.h"
#include "core/FlowConfiguration.h"

// Implemented in MiNiFiMain.cpp
void SignalExitProcess();

static const char* const SERVICE_TERMINATION_EVENT_NAME = "Global\\MiNiFiServiceTermination";

static void OutputDebug(const char* format, ...) {
  va_list args;
  va_start(args, format);

  char buf[256];
  sprintf_s(buf, _countof(buf), "%s: %s", SERVICE_NAME, format);

  char out[1024];
  StringCbVPrintfA(out, sizeof(out), buf, args);

  OutputDebugStringA(out);

  va_end(args);
};

void RunAsServiceIfNeeded() {
  static const int WAIT_TIME_EXE_TERMINATION = 5000;
  static const int WAIT_TIME_EXE_RESTART = 60000;

  static SERVICE_STATUS s_serviceStatus;
  static SERVICE_STATUS_HANDLE s_statusHandle;
  static HANDLE s_hProcess;
  static HANDLE s_hEvent;

  static auto Log = []() {
    static std::shared_ptr<logging::Logger> s_logger = logging::LoggerConfiguration::getConfiguration().getLogger("service");
    return s_logger;
  };

  SERVICE_TABLE_ENTRY serviceTable[] = {
    {
      SERVICE_NAME,
      [](DWORD argc, LPTSTR *argv) {
        setSyslogLogger();

        Log()->log_trace("ServiceCtrlDispatcher");

        s_hEvent = CreateEvent(0, TRUE, FALSE, SERVICE_TERMINATION_EVENT_NAME);
        if (!s_hEvent) {
          Log()->log_error("!CreateEvent lastError %x", GetLastError());
          return;
        }

        s_statusHandle = RegisterServiceCtrlHandler(
          SERVICE_NAME,
          [](DWORD ctrlCode) {
            Log()->log_trace("ServiceCtrlHandler ctrlCode %d", ctrlCode);

            if (SERVICE_CONTROL_STOP == ctrlCode) {
              Log()->log_trace("ServiceCtrlHandler ctrlCode = SERVICE_CONTROL_STOP");

              // Set service status SERVICE_STOP_PENDING.
              s_serviceStatus.dwControlsAccepted = 0;
              s_serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
              s_serviceStatus.dwWin32ExitCode = 0;

              if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
                Log()->log_error("!SetServiceStatus SERVICE_STOP_PENDING lastError %x", GetLastError());
              }

              bool exeTerminated = false;

              SetEvent(s_hEvent);

              Log()->log_info("Wait for exe termination");
              switch (auto res = WaitForSingleObject(s_hProcess, WAIT_TIME_EXE_TERMINATION)) {
                case WAIT_OBJECT_0:
                  Log()->log_info("Exe terminated");
                  exeTerminated = true;
                  break;

                case WAIT_TIMEOUT:
                  Log()->log_error("WaitForSingleObject timeout %d", WAIT_TIME_EXE_TERMINATION);
                  break;

                default:
                  Log()->log_error("!WaitForSingleObject return %d", res);
              }

              if (!exeTerminated) {
                Log()->log_info("TerminateProcess");
                if (TerminateProcess(s_hProcess, 0)) {
                  s_serviceStatus.dwControlsAccepted = 0;
                  s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
                  s_serviceStatus.dwWin32ExitCode = 0;

                  if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
                    Log()->log_error("!SetServiceStatus SERVICE_STOPPED lastError %x", GetLastError());
                  }
                } else {
                  Log()->log_error("!TerminateProcess lastError %x", GetLastError());
                }
              }
            }
          }
        );

        if (!s_statusHandle) {
          Log()->log_error("!RegisterServiceCtrlHandler lastError %x", GetLastError());
          return;
        }

        // Set service status SERVICE_START_PENDING.
        ZeroMemory(&s_serviceStatus, sizeof(s_serviceStatus));
        s_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        s_serviceStatus.dwControlsAccepted = 0;
        s_serviceStatus.dwCurrentState = SERVICE_START_PENDING;
        s_serviceStatus.dwWin32ExitCode = 0;
        s_serviceStatus.dwServiceSpecificExitCode = 0;

        if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
          Log()->log_error("!SetServiceStatus SERVICE_START_PENDING lastError %x", GetLastError());
          return;
        }

        // Set service status SERVICE_RUNNING.
        s_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
        s_serviceStatus.dwCurrentState = SERVICE_RUNNING;
        s_serviceStatus.dwWin32ExitCode = 0;

        if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
          Log()->log_error("!SetServiceStatus SERVICE_RUNNING lastError %x", GetLastError());

          // Set service status SERVICE_START_PENDING.
          s_serviceStatus.dwControlsAccepted = 0;
          s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
          s_serviceStatus.dwWin32ExitCode = GetLastError();

          if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
            Log()->log_error("!SetServiceStatus SERVICE_STOPPED lastError %x", GetLastError());
          }

          return;
        }

        char filePath[MAX_PATH];
        if (!GetModuleFileName(0, filePath, _countof(filePath))) {
          Log()->log_error("!GetModuleFileName lastError %x", GetLastError());
          return;
        }

        do {
          Log()->log_debug("Start exe path %s", filePath);

          STARTUPINFO startupInfo{};
          startupInfo.cb = sizeof(startupInfo);

          PROCESS_INFORMATION processInformation{};

          if (!CreateProcess(filePath, 0, 0, 0, 0, FALSE, 0, 0, &startupInfo, &processInformation)) {
            Log()->log_error("!CreateProcess lastError %x", GetLastError());
            return;
          }

          s_hProcess = processInformation.hProcess;

          Log()->log_info("%s started", filePath);

          auto res = WaitForSingleObject(processInformation.hProcess, INFINITE);
          CloseHandle(processInformation.hProcess);
          CloseHandle(processInformation.hThread);

          if (WAIT_FAILED == res) {
            Log()->log_error("!WaitForSingleObject hProcess lastError %x", GetLastError());
          } else if (WAIT_OBJECT_0 != res) {
            Log()->log_error("!WaitForSingleObject hProcess return %d", res);
          }

          Log()->log_info("Sleep %d sec before restarting exe", WAIT_TIME_EXE_RESTART/1000);
          res = WaitForSingleObject(s_hEvent, WAIT_TIME_EXE_RESTART);

          if (WAIT_OBJECT_0 == res) {
            Log()->log_info("Service was stopped, exe won't be restarted");
            break;
          }

          if (WAIT_FAILED == res) {
            Log()->log_error("!WaitForSingleObject s_hEvent lastError %x", GetLastError());
          } if (WAIT_TIMEOUT != res) {
            Log()->log_error("!WaitForSingleObject s_hEvent return %d", res);
          }
        } while (true);

        s_serviceStatus.dwControlsAccepted = 0;
        s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
        s_serviceStatus.dwWin32ExitCode = 0;

        if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
          Log()->log_error("!SetServiceStatus SERVICE_STOPPED lastError %x", GetLastError());
        }
      }
    },
    {0, 0}
  };

  if (!StartServiceCtrlDispatcher(serviceTable)) {
    if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == GetLastError()) {
      // Run this exe as console.
      return;
    }

    Log()->log_error("!StartServiceCtrlDispatcher lastError %x", GetLastError());

    ExitProcess(1);
  }

  Log()->log_info("Service exit");

  ExitProcess(0);
}

HANDLE GetTerminationEventHandle(bool* isStartedByService) {
  *isStartedByService = true;
  HANDLE hEvent = CreateEvent(0, TRUE, FALSE, SERVICE_TERMINATION_EVENT_NAME);
  if (!hEvent) {
    return nullptr;
  }

  if (GetLastError() != ERROR_ALREADY_EXISTS) {
    *isStartedByService = false;
  }

  return hEvent;
}

bool CreateServiceTerminationThread(std::shared_ptr<logging::Logger> logger, HANDLE terminationEventHandle) {
  // Get hService and monitor it - if service is terminated, then terminate current exe, otherwise the exe becomes unmanageable when service is restarted.
  auto hService = [&logger]() -> HANDLE {
    auto hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (INVALID_HANDLE_VALUE == hSnapShot) {
      logger->log_error("!CreateToolhelp32Snapshot lastError %x", GetLastError());
      return 0;
    }

    auto getProcessInfo = [&logger, &hSnapShot](DWORD processId, DWORD& parentProcessId, std::string& parentProcessName) {
      parentProcessId = 0;
      parentProcessName.clear();

      PROCESSENTRY32 procentry{};
      procentry.dwSize = sizeof(procentry);

      if (!Process32First(hSnapShot, &procentry)) {
        logger->log_error("!Process32First lastError %x", GetLastError());
        return;
      }

      do {
        if (processId == procentry.th32ProcessID) {
          parentProcessId = procentry.th32ParentProcessID;
          parentProcessName = procentry.szExeFile;
          return;
        }
      } while (Process32Next(hSnapShot, &procentry));
    };

    // Find current process info, which contains parentProcessId.
    DWORD parentProcessId{};
    std::string parentProcessName;
    getProcessInfo(GetCurrentProcessId(), parentProcessId, parentProcessName);

    // Find parent process info (the service which started current process), which contains service name.
    DWORD parentParentProcessId{};
    getProcessInfo(parentProcessId, parentParentProcessId, parentProcessName);

    CloseHandle(hSnapShot);

    // Just in case check that service name == current process name.
    char filePath[MAX_PATH];
    if (!GetModuleFileName(0, filePath, _countof(filePath))) {
      logger->log_error("!GetModuleFileName lastError %x", GetLastError());
      return 0;
    }

    const auto pSlash = strrchr(filePath, '\\');
    if (!pSlash) {
      logger->log_error("Invalid filePath %s", filePath);
      return 0;
    }
    const std::string fileName = pSlash + 1;

    if (_stricmp(fileName.c_str(), parentProcessName.c_str())) {
      logger->log_error("Parent process %s != current process %s", parentProcessName.c_str(), fileName.c_str());
      return 0;
    }

    const auto hParentProcess = OpenProcess(SYNCHRONIZE, FALSE, parentProcessId);
    if (!hParentProcess) {
      logger->log_error("!OpenProcess lastError %x", GetLastError());
      return 0;
    }

    return hParentProcess;
  }();
  if (!hService)
    return false;

  using ThreadInfo = std::tuple<std::shared_ptr<logging::Logger>, HANDLE, HANDLE>;
  auto pThreadInfo = new ThreadInfo(logger, terminationEventHandle, hService);

  HANDLE hThread = (HANDLE)_beginthreadex(
    0, 0,
    [](void* pPar) {
      const auto pThreadInfo = static_cast<ThreadInfo*>(pPar);
      const auto logger = std::get<0>(*pThreadInfo);
      const auto terminationEventHandle = std::get<1>(*pThreadInfo);
      const auto hService = std::get<2>(*pThreadInfo);
      delete pThreadInfo;

      HANDLE arHandle[] = { terminationEventHandle, hService };
      switch (auto res = WaitForMultipleObjects(_countof(arHandle), arHandle, FALSE, INFINITE)) {
        case WAIT_FAILED:
          logger->log_error("!WaitForSingleObject lastError %x", GetLastError());
        break;

        case WAIT_OBJECT_0:
          logger->log_info("Service event received");
        break;

        case WAIT_OBJECT_0 + 1:
          logger->log_info("Service is terminated");
        break;

        default:
          logger->log_info("WaitForMultipleObjects return %d", res);
      }

      SignalExitProcess();

      return 0U;
    },
    pThreadInfo,
    0, 0);
  if (!hThread) {
    logger->log_error("!_beginthreadex lastError %x", GetLastError());

    delete pThreadInfo;

    return false;
  }

  return true;
}

#endif
<|endoftext|>


<commit_before>/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "../include/pyext.h"
#include <omp.h>

using namespace std;

void makeJPEG(PyObject* _py_list_src, int idx, int _target_size, bool _crop_to_square, PyObject* _py_list_tgt);

static PyMethodDef _MakeDataPyExtMethods[] = {{ "resizeJPEG", resizeJPEG, METH_VARARGS },
                                              { NULL, NULL }
};

void init_MakeDataPyExt() {
    (void) Py_InitModule("_MakeDataPyExt", _MakeDataPyExtMethods);
}

PyObject* resizeJPEG(PyObject *self, PyObject *args) {

    PyListObject* pyListSrc;
    int tgtImgSize, numThreads;
    int cropToSquare;

    if (!PyArg_ParseTuple(args, "O!iii",
                          &PyList_Type, &pyListSrc,
                          &tgtImgSize,
                          &numThreads,
                          &cropToSquare)) {
        return NULL;
    }

    int num_imgs = PyList_GET_SIZE(pyListSrc);
    PyObject* pyListTgt = PyList_New(0);
    omp_set_num_threads(numThreads);
    #pragma omp parallel for
    for (int t = 0; t < num_imgs; ++t) {
        makeJPEG((PyObject*)pyListSrc, t, tgtImgSize, cropToSquare, pyListTgt);
    }

    return pyListTgt;
}
void makeJPEG(PyObject* _py_list_src, int idx, int _target_size, bool _crop_to_square, PyObject* _py_list_tgt) {
    cv::Mat _resized_mat_buffer;
    cv::cuda::GpuMat _resized_mat_buffer_gpu;
    std::vector<uchar> _output_jpeg_buffer;
    std::vector<int> _encode_params;
    _encode_params.push_back(CV_IMWRITE_JPEG_QUALITY);
    _encode_params.push_back(JPEG_QUALITY);

    /*
     * Decompress JPEG
     */
    PyObject* pySrc = PyList_GET_ITEM(_py_list_src, idx);
    uchar* src = (unsigned char*)PyString_AsString(pySrc);
    size_t src_len = PyString_GET_SIZE(pySrc);
    vector<uchar> src_vec(src, src + src_len);

    cv::Mat decoded_mat = cv::imdecode(cv::Mat(src_vec), CV_LOAD_IMAGE_COLOR);
    assert(decoded_mat.channels() == 3);

    // Load to GPU.
    cv::cuda::GpuMat decoded_mat_gpu;
    decoded_mat_gpu.upload(decoded_mat);

    /*
     * Resize
     */
    double min_dim = std::min(decoded_mat.size().height, decoded_mat.size().width);
    double scale_factor = _target_size / min_dim;

    int new_height = round(scale_factor * decoded_mat.size().height);
    int new_width = round(scale_factor * decoded_mat.size().width);
    assert((new_height == _target_size && new_width >= _target_size)
           || (new_width == _target_size && new_height >= _target_size));
    int interpolation = scale_factor == 1 ? cv::INTER_LINEAR
                      : scale_factor > 1 ? cv::INTER_CUBIC : cv::INTER_AREA;

    cv::cuda::resize(decoded_mat_gpu, _resized_mat_buffer_gpu, cv::Size(new_width, new_height), 0, 0, interpolation);
    _resized_mat_buffer_gpu.download(_resized_mat_buffer);

    /*
     * Conditionally crop and compress JPEG
     */
    if (_crop_to_square) {
        int crop_start_x = (new_width - _target_size) / 2;
        int crop_start_y = (new_height - _target_size) / 2;
        cv::Rect cropRect(crop_start_x, crop_start_y, _target_size, _target_size);
        cv::Mat cropped_mat_buffer = _resized_mat_buffer(cropRect);
        cv::imencode(".jpg", cropped_mat_buffer, _output_jpeg_buffer, _encode_params);
    } else {
        cv::imencode(".jpg", _resized_mat_buffer, _output_jpeg_buffer, _encode_params);
    }

    char* output_jpeg_buffer_ptr = reinterpret_cast<char*>(&_output_jpeg_buffer[0]);
    PyObject* pyStr = PyString_FromStringAndSize(output_jpeg_buffer_ptr, _output_jpeg_buffer.size());
    #pragma omp critical
    {
        PyList_Append(_py_list_tgt, pyStr);
    }
    Py_DECREF(pyStr);
}

<commit_msg>Add automatic no-GPU compatibility mode<commit_after>/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "../include/pyext.h"
#include <omp.h>

using namespace std;

void makeJPEG(PyObject* _py_list_src, int idx, int _target_size, bool _crop_to_square, PyObject* _py_list_tgt, bool withGpu);

static PyMethodDef _MakeDataPyExtMethods[] = {{ "resizeJPEG", resizeJPEG, METH_VARARGS },
                                              { NULL, NULL }
};

void init_MakeDataPyExt() {
    (void) Py_InitModule("_MakeDataPyExt", _MakeDataPyExtMethods);
}

PyObject* resizeJPEG(PyObject *self, PyObject *args) {

    PyListObject* pyListSrc;
    int tgtImgSize, numThreads;
    int cropToSquare;

    if (!PyArg_ParseTuple(args, "O!iii",
                          &PyList_Type, &pyListSrc,
                          &tgtImgSize,
                          &numThreads,
                          &cropToSquare)) {
        return NULL;
    }

    bool withGpu = cv::cuda::getCudaEnabledDeviceCount() > 0;
    int num_imgs = PyList_GET_SIZE(pyListSrc);
    PyObject* pyListTgt = PyList_New(0);
    omp_set_num_threads(numThreads);
    #pragma omp parallel for
    for (int t = 0; t < num_imgs; ++t) {
        makeJPEG((PyObject*)pyListSrc, t, tgtImgSize, cropToSquare, pyListTgt, withGpu);
    }

    return pyListTgt;
}
void makeJPEG(PyObject* _py_list_src, int idx, int _target_size, bool _crop_to_square, PyObject* _py_list_tgt, bool withGpu) {
    cv::Mat _resized_mat_buffer;
    cv::cuda::GpuMat _resized_mat_buffer_gpu;
    std::vector<uchar> _output_jpeg_buffer;
    std::vector<int> _encode_params;
    _encode_params.push_back(CV_IMWRITE_JPEG_QUALITY);
    _encode_params.push_back(JPEG_QUALITY);

    /*
     * Decompress JPEG
     */
    PyObject* pySrc = PyList_GET_ITEM(_py_list_src, idx);
    uchar* src = (unsigned char*)PyString_AsString(pySrc);
    size_t src_len = PyString_GET_SIZE(pySrc);
    vector<uchar> src_vec(src, src + src_len);

    cv::Mat decoded_mat = cv::imdecode(cv::Mat(src_vec), CV_LOAD_IMAGE_COLOR);
    assert(decoded_mat.channels() == 3);

    // Load to GPU.
    cv::cuda::GpuMat decoded_mat_gpu;
    if (withGpu) {
        decoded_mat_gpu.upload(decoded_mat);
    }

    /*
     * Resize
     */
    double min_dim = std::min(decoded_mat.size().height, decoded_mat.size().width);
    double scale_factor = _target_size / min_dim;

    int new_height = round(scale_factor * decoded_mat.size().height);
    int new_width = round(scale_factor * decoded_mat.size().width);
    assert((new_height == _target_size && new_width >= _target_size)
           || (new_width == _target_size && new_height >= _target_size));
    int interpolation = scale_factor == 1 ? cv::INTER_LINEAR
                      : scale_factor > 1 ? cv::INTER_CUBIC : cv::INTER_AREA;

    if (withGpu) {
        cv::cuda::resize(decoded_mat_gpu, _resized_mat_buffer_gpu, cv::Size(new_width, new_height), 0, 0, interpolation);
        _resized_mat_buffer_gpu.download(_resized_mat_buffer);
    } else {
        cv::resize(decoded_mat, _resized_mat_buffer, cv::Size(new_width, new_height), 0, 0, interpolation);
    }

    /*
     * Conditionally crop and compress JPEG
     */
    if (_crop_to_square) {
        int crop_start_x = (new_width - _target_size) / 2;
        int crop_start_y = (new_height - _target_size) / 2;
        cv::Rect cropRect(crop_start_x, crop_start_y, _target_size, _target_size);
        cv::Mat cropped_mat_buffer = _resized_mat_buffer(cropRect);
        cv::imencode(".jpg", cropped_mat_buffer, _output_jpeg_buffer, _encode_params);
    } else {
        cv::imencode(".jpg", _resized_mat_buffer, _output_jpeg_buffer, _encode_params);
    }

    char* output_jpeg_buffer_ptr = reinterpret_cast<char*>(&_output_jpeg_buffer[0]);
    PyObject* pyStr = PyString_FromStringAndSize(output_jpeg_buffer_ptr, _output_jpeg_buffer.size());
    #pragma omp critical
    {
        PyList_Append(_py_list_tgt, pyStr);
    }
    Py_DECREF(pyStr);
}

<|endoftext|>