1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-07-11 11:39:15 +00:00

Rewrite how commands from GUI are read

Instead of polling for input use a dedicated listener
thread to read commands from the GUI independently
from other threads.

To do this properly we have to delegate to the listener
all the reading from the GUI: while searching but also
while waiting for a command, like in std::getline().

So we have two possible behaviours: in-sync mode, in which
the thread mimics std::getline() and the caller blocks until
something is read from GUI, and async mode where the listener
continuously reads and processes GUI commands while other
threads are searching.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
This commit is contained in:
Marco Costalba 2011-11-05 07:53:19 +01:00
parent 22b9307aba
commit 2617aa415e
4 changed files with 188 additions and 65 deletions

View file

@ -439,6 +439,10 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) {
<< endl; << endl;
} }
// Start async mode to catch UCI commands sent to us while searching,
// like "quit", "stop", etc.
Threads.start_listener();
// We're ready to start thinking. Call the iterative deepening loop function // We're ready to start thinking. Call the iterative deepening loop function
Move ponderMove = MOVE_NONE; Move ponderMove = MOVE_NONE;
Move bestMove = id_loop(pos, searchMoves, &ponderMove); Move bestMove = id_loop(pos, searchMoves, &ponderMove);
@ -462,6 +466,9 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) {
// This makes all the threads to go to sleep // This makes all the threads to go to sleep
Threads.set_size(1); Threads.set_size(1);
// From now on any UCI command will be read in-sync with Threads.getline()
Threads.stop_listener();
// If we are pondering or in infinite search, we shouldn't print the // If we are pondering or in infinite search, we shouldn't print the
// best move before we are told to do so. // best move before we are told to do so.
if (!StopRequest && (Limits.ponder || Limits.infinite)) if (!StopRequest && (Limits.ponder || Limits.infinite))
@ -1912,38 +1919,6 @@ split_point_start: // At split points actual search starts from here
static int lastInfoTime; static int lastInfoTime;
int t = current_search_time(); int t = current_search_time();
// Poll for input
if (input_available())
{
// We are line oriented, don't read single chars
string command;
if (!std::getline(std::cin, command) || command == "quit")
{
// Quit the program as soon as possible
Limits.ponder = false;
QuitRequest = StopRequest = true;
return;
}
else if (command == "stop")
{
// Stop calculating as soon as possible, but still send the "bestmove"
// and possibly the "ponder" token when finishing the search.
Limits.ponder = false;
StopRequest = true;
}
else if (command == "ponderhit")
{
// The opponent has played the expected move. GUI sends "ponderhit" if
// we were told to ponder on the same move the opponent has played. We
// should continue searching but switching from pondering to normal search.
Limits.ponder = false;
if (StopOnPonderhit)
StopRequest = true;
}
}
// Print search information // Print search information
if (t < 1000) if (t < 1000)
lastInfoTime = 0; lastInfoTime = 0;
@ -1988,14 +1963,14 @@ split_point_start: // At split points actual search starts from here
void wait_for_stop_or_ponderhit() { void wait_for_stop_or_ponderhit() {
string command; string cmd;
// Wait for a command from stdin // Wait for a command from stdin
while ( std::getline(std::cin, command) while (cmd != "ponderhit" && cmd != "stop" && cmd != "quit")
&& command != "ponderhit" && command != "stop" && command != "quit") {}; Threads.getline(cmd);
if (command != "ponderhit" && command != "stop") if (cmd == "quit")
QuitRequest = true; // Must be "quit" or getline() returned false QuitRequest = true;
} }
@ -2248,3 +2223,34 @@ void Thread::idle_loop(SplitPoint* sp) {
} }
} }
} }
// ThreadsManager::do_uci_async_cmd() processes the commands from GUI received
// by listener thread while the other threads are searching.
void ThreadsManager::do_uci_async_cmd(const std::string& cmd) {
if (cmd == "quit")
{
// Quit the program as soon as possible
Limits.ponder = false;
QuitRequest = StopRequest = true;
}
else if (cmd == "stop")
{
// Stop calculating as soon as possible, but still send the "bestmove"
// and possibly the "ponder" token when finishing the search.
Limits.ponder = false;
StopRequest = true;
}
else if (cmd == "ponderhit")
{
// The opponent has played the expected move. GUI sends "ponderhit" if
// we were told to ponder on the same move the opponent has played. We
// should continue searching but switching from pondering to normal search.
Limits.ponder = false;
if (StopOnPonderhit)
StopRequest = true;
}
}

View file

@ -27,28 +27,23 @@ ThreadsManager Threads; // Global object definition
namespace { extern "C" { namespace { extern "C" {
// start_routine() is the C function which is called when a new thread // start_routine() is the C function which is called when a new thread
// is launched. It simply calls idle_loop() of the supplied thread. // is launched. It simply calls idle_loop() of the supplied thread. The
// There are two versions of this function; one for POSIX threads and // last thread is dedicated to I/O and so runs in listener_loop().
// one for Windows threads.
#if defined(_MSC_VER) #if defined(_MSC_VER)
DWORD WINAPI start_routine(LPVOID thread) { DWORD WINAPI start_routine(LPVOID thread) {
#else
void* start_routine(void* thread) {
#endif
if (((Thread*)thread)->threadID == MAX_THREADS)
((Thread*)thread)->listener_loop();
else
((Thread*)thread)->idle_loop(NULL);
((Thread*)thread)->idle_loop(NULL);
return 0; return 0;
} }
#else
void* start_routine(void* thread) {
((Thread*)thread)->idle_loop(NULL);
return NULL;
}
#endif
} } } }
@ -147,11 +142,14 @@ void ThreadsManager::set_size(int cnt) {
void ThreadsManager::init() { void ThreadsManager::init() {
// Initialize sleep condition used to block waiting for GUI input
cond_init(&sleepCond);
// Initialize threads lock, used when allocating slaves during splitting // Initialize threads lock, used when allocating slaves during splitting
lock_init(&threadsLock); lock_init(&threadsLock);
// Initialize sleep and split point locks // Initialize sleep and split point locks
for (int i = 0; i < MAX_THREADS; i++) for (int i = 0; i <= MAX_THREADS; i++)
{ {
lock_init(&threads[i].sleepLock); lock_init(&threads[i].sleepLock);
cond_init(&threads[i].sleepCond); cond_init(&threads[i].sleepCond);
@ -167,7 +165,7 @@ void ThreadsManager::init() {
// Create and launch all the threads but the main that is already running, // Create and launch all the threads but the main that is already running,
// threads will go immediately to sleep. // threads will go immediately to sleep.
for (int i = 1; i < MAX_THREADS; i++) for (int i = 1; i <= MAX_THREADS; i++)
{ {
threads[i].is_searching = false; threads[i].is_searching = false;
threads[i].threadID = i; threads[i].threadID = i;
@ -192,18 +190,13 @@ void ThreadsManager::init() {
void ThreadsManager::exit() { void ThreadsManager::exit() {
// Wake up all the slave threads at once. This is faster than "wake and wait" for (int i = 0; i <= MAX_THREADS; i++)
// for each thread and avoids a rare crash once every 10K games under Linux.
for (int i = 1; i < MAX_THREADS; i++)
{
threads[i].do_terminate = true;
threads[i].wake_up();
}
for (int i = 0; i < MAX_THREADS; i++)
{ {
if (i != 0) if (i != 0)
{ {
threads[i].do_terminate = true;
threads[i].wake_up();
// Wait for slave termination // Wait for slave termination
#if defined(_MSC_VER) #if defined(_MSC_VER)
WaitForSingleObject(threads[i].handle, 0); WaitForSingleObject(threads[i].handle, 0);
@ -222,6 +215,7 @@ void ThreadsManager::exit() {
} }
lock_destroy(&threadsLock); lock_destroy(&threadsLock);
cond_destroy(&sleepCond);
} }
@ -353,3 +347,113 @@ Value ThreadsManager::split(Position& pos, SearchStack* ss, Value alpha, Value b
// Explicit template instantiations // Explicit template instantiations
template Value ThreadsManager::split<false>(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int); template Value ThreadsManager::split<false>(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int);
template Value ThreadsManager::split<true>(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int); template Value ThreadsManager::split<true>(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int);
// Thread::listner_loop() is where the last thread, used for IO, waits for input.
// Input is read in sync with main thread (that blocks) when is_searching is set
// to false, otherwise IO thread reads any input asynchronously and processes
// the input line calling do_uci_async_cmd().
void Thread::listener_loop() {
std::string cmd;
while (true)
{
lock_grab(&sleepLock);
Threads.inputLine = cmd;
do_sleep = !is_searching;
// Here the thread is parked in sync mode after a line has been read
while (do_sleep && !do_terminate) // Catches spurious wake ups
{
cond_signal(&Threads.sleepCond); // Wake up main thread
cond_wait(&sleepCond, &sleepLock); // Sleep here
}
lock_release(&sleepLock);
if (do_terminate)
return;
if (!std::getline(std::cin, cmd)) // Block waiting for input
cmd = "quit";
lock_grab(&sleepLock);
// If we are in async mode then process the command now
if (is_searching)
{
// Command "quit" is the last one received by the GUI, so park the
// thread waiting for exiting.
if (cmd == "quit")
is_searching = false;
Threads.do_uci_async_cmd(cmd);
cmd = ""; // Input has been consumed
}
lock_release(&sleepLock);
}
}
// ThreadsManager::getline() is used by main thread to block and wait for input,
// the behaviour mimics std::getline().
void ThreadsManager::getline(std::string& cmd) {
Thread& listener = threads[MAX_THREADS];
lock_grab(&listener.sleepLock);
listener.is_searching = false; // Set sync mode
// If there is already some input to grab then skip without to wake up the
// listener. This can happen if after we send the "bestmove", the GUI sends
// a command that the listener buffers in inputLine before going to sleep.
if (inputLine.empty())
{
listener.do_sleep = false;
cond_signal(&listener.sleepCond); // Wake up listener thread
while (!listener.do_sleep)
cond_wait(&sleepCond, &listener.sleepLock); // Wait for input
}
cmd = inputLine;
inputLine = ""; // Input has been consumed
lock_release(&listener.sleepLock);
}
// ThreadsManager::start_listener() is called at the beginning of the search to
// swith from sync behaviour (default) to async and so be able to read from UCI
// while other threads are searching. This avoids main thread polling for input.
void ThreadsManager::start_listener() {
Thread& listener = threads[MAX_THREADS];
lock_grab(&listener.sleepLock);
listener.is_searching = true;
listener.do_sleep = false;
cond_signal(&listener.sleepCond); // Wake up listener thread
lock_release(&listener.sleepLock);
}
// ThreadsManager::stop_listener() is called before to send "bestmove" to GUI to
// return to in-sync behaviour. This is needed because while in async mode any
// command is discarded without being processed (except for a very few ones).
void ThreadsManager::stop_listener() {
Thread& listener = threads[MAX_THREADS];
lock_grab(&listener.sleepLock);
listener.is_searching = false;
lock_release(&listener.sleepLock);
}

View file

@ -69,6 +69,7 @@ struct Thread {
bool cutoff_occurred() const; bool cutoff_occurred() const;
bool is_available_to(int master) const; bool is_available_to(int master) const;
void idle_loop(SplitPoint* sp); void idle_loop(SplitPoint* sp);
void listener_loop();
SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS]; SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS];
MaterialInfoTable materialTable; MaterialInfoTable materialTable;
@ -113,16 +114,25 @@ public:
void read_uci_options(); void read_uci_options();
bool available_slave_exists(int master) const; bool available_slave_exists(int master) const;
void getline(std::string& cmd);
void do_uci_async_cmd(const std::string& cmd);
void start_listener();
void stop_listener();
template <bool Fake> template <bool Fake>
Value split(Position& pos, SearchStack* ss, Value alpha, Value beta, Value bestValue, Value split(Position& pos, SearchStack* ss, Value alpha, Value beta, Value bestValue,
Depth depth, Move threatMove, int moveCount, MovePicker* mp, int nodeType); Depth depth, Move threatMove, int moveCount, MovePicker* mp, int nodeType);
private: private:
Thread threads[MAX_THREADS]; friend struct Thread;
Thread threads[MAX_THREADS + 1];
Lock threadsLock; Lock threadsLock;
Depth minimumSplitDepth; Depth minimumSplitDepth;
int maxThreadsPerSplitPoint; int maxThreadsPerSplitPoint;
int activeThreads; int activeThreads;
bool useSleepingThreads; bool useSleepingThreads;
WaitCondition sleepCond;
std::string inputLine;
}; };
extern ThreadsManager Threads; extern ThreadsManager Threads;

View file

@ -28,6 +28,7 @@
#include "move.h" #include "move.h"
#include "position.h" #include "position.h"
#include "search.h" #include "search.h"
#include "thread.h"
#include "ucioption.h" #include "ucioption.h"
using namespace std; using namespace std;
@ -60,8 +61,10 @@ void uci_loop() {
string cmd, token; string cmd, token;
bool quit = false; bool quit = false;
while (!quit && getline(cin, cmd)) while (!quit)
{ {
Threads.getline(cmd);
istringstream is(cmd); istringstream is(cmd);
is >> skipws >> token; is >> skipws >> token;