UCI on another thread , programming help !

Discussion of chess software programming and technical issues.

Moderator: Ras

MahmoudUthman
Posts: 237
Joined: Sat Jan 17, 2015 11:54 pm

UCI on another thread , programming help !

Post by MahmoudUthman »

I decided use a mini UCI loop during search to keep the engine listing for input all the time , here my code :
search.h

Code: Select all

#pragma once
#include<atomic>
static std::atomic_bool stop=false;
namespace Search
{
	int Alphabeta()
	{
		int r = 10;
		int Rounds = 0;
		while ((++Rounds<20)&&!stop)
		{
			int sum = 0;
			for (size_t i = 0; i < 100000000; i++)
		{
				sum += i;
		}

			sync_cout << sum<< sync_endl;

		}
		sync_cout << "Stopping" << sync_endl;
		return r;
	}
}
uci.h

Code: Select all

#pragma once
#include<iostream>
#include<string>
#include <sstream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<Windows.h>

enum SyncCout { IO_LOCK, IO_UNLOCK };
//taken from StockFish
std::ostream& operator<<(std::ostream& os, SyncCout sc) {

	static std::mutex m;

	if (sc == IO_LOCK) m.lock();

	if (sc == IO_UNLOCK) m.unlock();

	return os;
}
#define sync_cout std::cout << IO_LOCK
#define sync_endl std::endl << IO_UNLOCK

#include"search.h"

class Thread
{
public:
	Thread()
	{
		sync_cout << "@Consturcting" << sync_endl;
		nativeThread = std::thread(&Thread::MainLoop, this);

		hConIn = GetStdHandle(STD_INPUT_HANDLE);

		ir.EventType = KEY_EVENT;
		ir.Event.KeyEvent.bKeyDown = TRUE;
		ir.Event.KeyEvent.dwControlKeyState = 0;
		ir.Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
		ir.Event.KeyEvent.wRepeatCount = 1;
		ir.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
		ir.Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
	}

	void MainLoop()
	{
		while (!exit)
		{
			while (pause)
			{
				sync_cout << "@Sleeping" << sync_endl;
				std::unique_lock<std::mutex> lk(mutex);
				cv.wait(lk);
				lk.unlock();
			}

			std::string token, cmd;
			while (!pause && !exit && std::getline(std::cin, cmd))
			{
				sync_cout << "@Working" << sync_endl;
				std::istringstream is(cmd);
				is >> std::skipws >> token;
				if (token == "stop")
				{
					stop = true;
					PauseThread();
				}
				else if (token == "isready") std::cout << "readyok" << std::endl;
				else if (token == "") PauseThread();
			}
		}

	}

	void PauseThread()
	{
		//pause
		std::lock_guard<std::mutex> lk(mutex);
		pause = true;
	}

	void ResumeThread()
	{
		std::lock_guard<std::mutex> lk(mutex);
		pause = false;
		cv.notify_one();
	}

	void SignalSearchEnd()
	{
		DWORD dwTmp = 0;
		WriteConsoleInput(hConIn, &ir, 1, &dwTmp);
		FlushConsoleInputBuffer(hConIn);//check if not needed ?
	}

	~Thread()
	{
		sync_cout << "@destructing" << sync_endl;
		mutex.lock();
		pause = false;
		exit = true;
		cv.notify_one();
		mutex.unlock();
		nativeThread.join();
	}

private:
	HANDLE hConIn;
	INPUT_RECORD ir;
	std::thread nativeThread;
	std::mutex mutex;
	std::condition_variable cv;
	bool pause = true, exit = false;
};




namespace UCI
{
	void Loop()
	{
		Thread th;
		


		std::string token, cmd;
		while (std::getline(std::cin, cmd))
		{
			std::cout << "Loop : ";
			std::istringstream is(cmd);
			is >> std::skipws >> token;
			if (token == "go")
			{
				std::cout << "go start" << std::endl;
				stop = false;

				th.ResumeThread();
				
				Search::Alphabeta();
				th.SignalSearchEnd();				

				std::cout << "go end" << std::endl;
			}
			else if (token == "isready") std::cout << "readyok" << std::endl;
			else if (token == "quiet")
			{
				std::cout << "quieting" << std::endl;
				break;
			}
		}
	}


	
}
main.cpp

Code: Select all

#include"UCI.h"
int main()
{
	UCI::Loop();
}
this is just a template to test the idea , but the main problem I'm having is when the search ends without interruption from the gui , then the secondary thread would be stuck listing for input until another command "which could be very well in supported for there" arrive , only then would it stop and possibly resulting in the loss of the command "since the main UCI loop wasn't the one to receive it" .
The only solution I found so far is writing to the stdin directly but it's so ugly and completely unportable , is there a better way and if not what are the equivalent option for Linux and other popular os' ?
*Ps, I know this is more suited to SO but I think I may get more answers here if someone tried the same thing before
User avatar
hgm
Posts: 28454
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: UCI on another thread , programming help !

Post by hgm »

I always poll for input, and/or completion time from the Search routine. When the input cannot be processed during search, I leave it in the input buffer. The ReadLine() function used in the main loop first checks if there is something in the input buffer, and only reads a line from stdin if there wasn't. After that it processes the line.

Note that in UCI you must ignore commands that are not supposed to come. When searching you are basically waiting for 'quit', 'stop', 'ponderhit', 'isready' or 'debug'. The latter three can be handled in search, while 'stop' should terminate the search, and 'quit' the process. Other commands are not legal, and thus should be ignored. You should not try to postpone their processing until after receiving 'stop'.
elcabesa
Posts: 858
Joined: Sun May 23, 2010 1:32 pm

Re: UCI on another thread , programming help !

Post by elcabesa »

I think you have to change a little your architecture.

you have 2 thread:
1) uci loop which receive "go" and became an alpha-beta searcher
2) a small uci loop launched when the first thread became an alpha-beta searcher.

another solution is the following:
1) uci loop thread doing only uci
2) alpha-beta seracher thread used only for search.

the alpha beta thread shall sleep until you received a go command. at this point the uci thread collect the info, pass them to the alpha beta thread and then resume it.

this way you have each thread doing only one kind of work.

you could extend this to 3 thread
1) uci loop ( alway blocked waiting getLine)
2) alpha beta searcher thread
3) time management thread :)

i think the latest is the stockfish solution.
Dirt
Posts: 2851
Joined: Wed Mar 08, 2006 10:01 pm
Location: Irvine, CA, USA

Re: UCI on another thread , programming help !

Post by Dirt »

MahmoudUthman wrote:*Ps, I know this is more suited to SO but I think I may get more answers here if someone tried the same thing before
What is SO? In any case, it belongs here in the Programming thread.
Deasil is the right way to go.
Rein Halbersma
Posts: 751
Joined: Tue May 22, 2007 11:13 am

Re: UCI on another thread , programming help !

Post by Rein Halbersma »

Dirt wrote:
MahmoudUthman wrote:*Ps, I know this is more suited to SO but I think I may get more answers here if someone tried the same thing before
What is SO? In any case, it belongs here in the Programming thread.
http://stackoverflow.com
MahmoudUthman
Posts: 237
Joined: Sat Jan 17, 2015 11:54 pm

Re: UCI on another thread , programming help !

Post by MahmoudUthman »

hgm wrote:I always poll for input, and/or completion time from the Search routine. When the input cannot be processed during search, I leave it in the input buffer. The ReadLine() function used in the main loop first checks if there is something in the input buffer, and only reads a line from stdin if there wasn't. After that it processes the line.

Note that in UCI you must ignore commands that are not supposed to come. When searching you are basically waiting for 'quit', 'stop', 'ponderhit', 'isready' or 'debug'. The latter three can be handled in search, while 'stop' should terminate the search, and 'quit' the process. Other commands are not legal, and thus should be ignored. You should not try to postpone their processing until after receiving 'stop'.
how often should I do that & isn't polling for input during search inefficient ?
As for postponing I didn't mean by possible loss of a command one that comes while searching but due to the aforementioned problem in my implementation the listing loop actually remains waiting for one more input after the search has exited since "stop" was never received and won't be the next command from the GUI.
elcabesa wrote:I think you have to change a little your architecture.

you have 2 thread:
1) uci loop which receive "go" and became an alpha-beta searcher
2) a small uci loop launched when the first thread became an alpha-beta searcher.

another solution is the following:
1) uci loop thread doing only uci
2) alpha-beta seracher thread used only for search.

the alpha beta thread shall sleep until you received a go command. at this point the uci thread collect the info, pass them to the alpha beta thread and then resume it.

this way you have each thread doing only one kind of work.

you could extend this to 3 thread
1) uci loop ( alway blocked waiting getLine)
2) alpha beta searcher thread
3) time management thread :)

i think the latest is the stockfish solution.
regarding the complete separation of UCI and search threads , consider this example all the state variables (position representation , history , killer ,etc..) are global if in the main thread I listen to UCI and when I receive a go command I launch the search function using an idle search-thread or just using std::async do I need to worry about synchronization or atomicity of those global variables for example a variable modified by the uci loop being read by the search function from cache instead of memory or something ? "I'm still new to threading in general and especially in c++"
lucasart
Posts: 3243
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: UCI on another thread , programming help !

Post by lucasart »

MahmoudUthman wrote:I decided use a mini UCI loop during search to keep the engine listing for input all the time , here my code :
search.h

Code: Select all

#pragma once
#include<atomic>
static std::atomic_bool stop=false;
namespace Search
{
	int Alphabeta()
	{
		int r = 10;
		int Rounds = 0;
		while ((++Rounds<20)&&!stop)
		{
			int sum = 0;
			for (size_t i = 0; i < 100000000; i++)
		{
				sum += i;
		}

			sync_cout << sum<< sync_endl;

		}
		sync_cout << "Stopping" << sync_endl;
		return r;
	}
}
uci.h

Code: Select all

#pragma once
#include<iostream>
#include<string>
#include <sstream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<Windows.h>

enum SyncCout { IO_LOCK, IO_UNLOCK };
//taken from StockFish
std::ostream& operator<<(std::ostream& os, SyncCout sc) {

	static std::mutex m;

	if (sc == IO_LOCK) m.lock();

	if (sc == IO_UNLOCK) m.unlock();

	return os;
}
#define sync_cout std::cout << IO_LOCK
#define sync_endl std::endl << IO_UNLOCK

#include"search.h"

class Thread
{
public:
	Thread()
	{
		sync_cout << "@Consturcting" << sync_endl;
		nativeThread = std::thread(&Thread::MainLoop, this);

		hConIn = GetStdHandle(STD_INPUT_HANDLE);

		ir.EventType = KEY_EVENT;
		ir.Event.KeyEvent.bKeyDown = TRUE;
		ir.Event.KeyEvent.dwControlKeyState = 0;
		ir.Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
		ir.Event.KeyEvent.wRepeatCount = 1;
		ir.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
		ir.Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
	}

	void MainLoop()
	{
		while (!exit)
		{
			while (pause)
			{
				sync_cout << "@Sleeping" << sync_endl;
				std::unique_lock<std::mutex> lk(mutex);
				cv.wait(lk);
				lk.unlock();
			}

			std::string token, cmd;
			while (!pause && !exit && std::getline(std::cin, cmd))
			{
				sync_cout << "@Working" << sync_endl;
				std::istringstream is(cmd);
				is >> std::skipws >> token;
				if (token == "stop")
				{
					stop = true;
					PauseThread();
				}
				else if (token == "isready") std::cout << "readyok" << std::endl;
				else if (token == "") PauseThread();
			}
		}

	}

	void PauseThread()
	{
		//pause
		std::lock_guard<std::mutex> lk(mutex);
		pause = true;
	}

	void ResumeThread()
	{
		std::lock_guard<std::mutex> lk(mutex);
		pause = false;
		cv.notify_one();
	}

	void SignalSearchEnd()
	{
		DWORD dwTmp = 0;
		WriteConsoleInput(hConIn, &ir, 1, &dwTmp);
		FlushConsoleInputBuffer(hConIn);//check if not needed ?
	}

	~Thread()
	{
		sync_cout << "@destructing" << sync_endl;
		mutex.lock();
		pause = false;
		exit = true;
		cv.notify_one();
		mutex.unlock();
		nativeThread.join();
	}

private:
	HANDLE hConIn;
	INPUT_RECORD ir;
	std::thread nativeThread;
	std::mutex mutex;
	std::condition_variable cv;
	bool pause = true, exit = false;
};




namespace UCI
{
	void Loop()
	{
		Thread th;
		


		std::string token, cmd;
		while (std::getline(std::cin, cmd))
		{
			std::cout << "Loop : ";
			std::istringstream is(cmd);
			is >> std::skipws >> token;
			if (token == "go")
			{
				std::cout << "go start" << std::endl;
				stop = false;

				th.ResumeThread();
				
				Search::Alphabeta();
				th.SignalSearchEnd();				

				std::cout << "go end" << std::endl;
			}
			else if (token == "isready") std::cout << "readyok" << std::endl;
			else if (token == "quiet")
			{
				std::cout << "quieting" << std::endl;
				break;
			}
		}
	}


	
}
main.cpp

Code: Select all

#include"UCI.h"
int main()
{
	UCI::Loop();
}
this is just a template to test the idea , but the main problem I'm having is when the search ends without interruption from the gui , then the secondary thread would be stuck listing for input until another command "which could be very well in supported for there" arrive , only then would it stop and possibly resulting in the loss of the command "since the main UCI loop wasn't the one to receive it" .
The only solution I found so far is writing to the stdin directly but it's so ugly and completely unportable , is there a better way and if not what are the equivalent option for Linux and other popular os' ?
*Ps, I know this is more suited to SO but I think I may get more answers here if someone tried the same thing before
First solve the problem. Then write the code.

OK, this sounds pedantic, but it's a serious advice. Without copy/pasting any of your code, can you describe how it works ? With sentences, or pseudo-code ?

PS: You don't need condition variables. It's an unnecessary complication. Just create/join threads as needed. And you certainly don't need all this ghastly Windows API code.
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.
User avatar
hgm
Posts: 28454
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: UCI on another thread , programming help !

Post by hgm »

MahmoudUthman wrote:how often should I do that & isn't polling for input during search inefficient ?
I usually poll once every 4000 nodes, either for time or for input. But you can make it dependent on the TC. Determining when it is time to poll can be combined with node counting:

Code: Select all

int count;
int64 nodeCount; // actual node count is nodeCount+count

Search()
{
  if(count++ == 0) {
    count = -INTERVAL;
    nodeCnt += INTERVAL;
    ProbeForTimeOrInput();
  }
  ...
This requires just one easily predicted branch after the increment that was done anyway. Only once every INTERVAL you have a mispredict, and some extra work is done, including the probe.

It is true that this requires some overhead. But except for ultra-fast TC you can make INTERVAL so large that this is quite negligible. And who cares about ultra-fast TC? There are no tournaments or rating lists for such TC.

You can eliminate the overhead, but keep the same logic with a second thread. By having that thread block while waiting for input, and write it into a global buffer once it arrives. And after that set a global flag, which ProbeForTimeOrInput() would test. If it finds it set, it can then process the input, and possibly abort the search.

You would need some thread synchronization, though, to have the input thread refrain from reading new input before the previous input has been processed. (Which might involve terminating the search first.) I am sure there are dedicated semaphores for that, but in UCI2WB I abuse an internal pipe for that purpose: when a thread must wait for another thread to complete a task, I just set it reading a single character from that pipe. After completing the task the other thread then writes a character into that pipe, which unblocks the waiting thread.

Code: Select all

char *inBuf;

void
InputProc ()
{
  while(1) {
    inBuf = ReadLine(stdin);
    getc(syncPipeExit);
  }
}

int
TestForInput ()
{
  if(!inBuf) return FALSE;
  if(!ProcessCommand(inBuf)) { // returns TRUE if command was processed
    // command requires search abort
    return TRUE; // this is supposed to request search abortion
  }
  inBuf = NULL;
  putc('0', syncPipeEntrance); // wake up input thread
  return FALSE;
}