Page 1 of 1

Listening for GUI input when searching

Posted: Tue Apr 27, 2021 5:20 pm
by niel5946
Hi.

I am currently trying to find a way of making a separate thread listen for UCI-commands on stdin while searching, but I can't quite figure it out...
How do you do this?

I have tried having a global structure object that holds a "stop" and a "quit" flag, that gets set to true when we're told to stop or quit respectively. Then, before launching the search, I start a thread that runs the following function that listens for UCI input:

Code: Select all

void COMM::listen_for_input(bool stopset, long long stoptime) {
    std::string input = "";

    while (!stopset || getTimeMs() < stoptime) {
        input = "";
        std::getline(std::cin, input);


        if (input.find("quit") != std::string::npos) {
            ld.quit = true;
            return;
        }
    
        if (input.find("stop") != std::string::npos) {
            ld.stop = true;
            return;
        }

        if ((stopset && getTimeMs() >= stoptime) || Search::isStop.load(std::memory_order_relaxed)) {
            return;
        }

    }
}
Then, when the search-threads check for an aborted search they call the following function:

Code: Select all

void COMM::check_stopped_search(SearchThread_t* ss) {

	// Step 1. Check for timed out search
	if (ss->info->timeset && getTimeMs() >= ss->info->stoptime) {
		ss->info->stopped = true;
	}

	// Step 2. Check for input from the GUI telling us to quit or stop. Only do this if we're the main thread.
	if (ss->thread_id == 0) {

		// Step 2A. Check for stop command
		if (ld.stop) {
			ss->info->stopped = true;
		}

		// Step 2B. Check for quit command
		if (ld.quit) {
			ss->info->quit = true;
			ss->info->stopped = true;
		}

		// Step 2C. If we've been told to stop or quit, tell the other threads with the std::atomic<bool> isStop flag
		if (ss->info->stopped) {
			Search::isStop = true;
		}
	}

	// Step 3. If we're not the main thread, check to see if we've been told to stop.
	else {
		if (Search::isStop.load(std::memory_order_relaxed)) {
			ss->info->stopped = true;
		}
	}
}
Where ld is the global object holding the "quit" and "stop" booleans. I would think such a global object is alright to use since only one thread ever writes to it?
My problem is that when I try to join the listener thread, it only does so if it specifically recieves a "stop" or "quit" command (but the engine doesn't even crash; it just sits there doing nothing). It doesn't stop automatically even though I make it return if it times out... And I really can't see what the problem is :(
Can anyone see the error? How is this input-listening usually done when searching?

Thanks in advance :D

Re: Listening for GUI input when searching

Posted: Tue Apr 27, 2021 10:17 pm
by hgm
My engines just poll for input every 4096 nodes from the search thread (using PeekNamedPipe() on Windows). If there is input they read it. If it is a ponderhit they search on after changing the state from ponder to think. On other commands they set the abort flag, and leave the command in the input buffer, so that the main command loop will execute them after the search terminates.

Re: Listening for GUI input when searching

Posted: Tue Apr 27, 2021 10:29 pm
by Sven
Niels, please see my reply in your other thread.

Re: Listening for GUI input when searching

Posted: Wed Apr 28, 2021 8:22 am
by niel5946
hgm wrote: Tue Apr 27, 2021 10:17 pm My engines just poll for input every 4096 nodes from the search thread (using PeekNamedPipe() on Windows). If there is input they read it. If it is a ponderhit they search on after changing the state from ponder to think. On other commands they set the abort flag, and leave the command in the input buffer, so that the main command loop will execute them after the search terminates.
But I don't understand, how do you then collect the commands between every 4096 nodes? I mean, if the search threads only check for input instantaneously, there is a big probability for the command coming between two checks. How would your implementation pick up such commands?
Sven wrote: Tue Apr 27, 2021 10:29 pm Niels, please see my reply in your other thread.
Yes, thank you. I just saw it now

Re: Listening for GUI input when searching

Posted: Wed Apr 28, 2021 8:56 am
by hgm
niel5946 wrote: Wed Apr 28, 2021 8:22 amBut I don't understand, how do you then collect the commands between every 4096 nodes? I mean, if the search threads only check for input instantaneously, there is a big probability for the command coming between two checks. How would your implementation pick up such commands?
That is not how pipes work. Any input the GUI sends to the engine stays in the pipe (which is just a FIFO buffer managed by the OS) until the engine reads it. PeekNamedPipe() tells you how many characters there are in this buffer. So every 4096 nodes the engine polls to see if something has arrived since the previous time check. As soon as it sees there is input waiting, it can start reading and processing commands without being in danger of blocking its execution.

The most convenient way I found to implement this is to make a routine ProcessOneCommand(Boolean searching), which

1) Checks if the input buffer is empty, and if so, reads a line from stdin to fill it.
2) Calls exit() on EOF.
3) Tests for a ponder hit, in which case it changes the search to a timed one, clears the command from the buffer, and returns.
4) Sets the abort flag, and returns if searching == TRUE.
5) Executes the command, and clears the input buffer.

Every 4096 nodes the Search() routine then checks whether it should time out, and if not, whether there is input pending. If the latter is the case it calls ProcessOneCommand(TRUE). The main program is a loop calling ProcessOneCommand(FALSE). The abort flag is checked in every node, right after UnMake() and causes Search() to return.

Re: Listening for GUI input when searching

Posted: Wed Apr 28, 2021 9:27 am
by niel5946
That seems like a good way of doing it. I don't really know a lot about pipes and I/O in general, but if this multi-threaded approach (that I've discussed in Progress on Loki) doesn't work, I will give this a go.
Another reason that I'm a little hesitant of this method is because that is the one I am currently using, which is taken form Vice. Since the UCI-rework was based on a desire to remove as much code from Vice as possible, I want to try out other methods first. Otherwise I might be tempted to just keep the current implementations...

Thank you for your help :D