Buffer 'setoption' commands until engine done thinking
authorH.G.Muller <hgm@hgm-xboard.(none)>
Thu, 1 Dec 2016 20:17:11 +0000 (21:17 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Thu, 1 Dec 2016 22:27:32 +0000 (23:27 +0100)
UCI does not allow setting of options during search after all. So all
'setoption' commands are now first collected in a memory buffer. During
ponder or analysis the search is then stopped immediately to flush that
buffer to the engine, in a new routine Release(). This routine will
also (re)start a ponder search if one is called for. During thinking,
finishing the search has priority. So only Release() the buffer contents
when a search finishes (immediately after receiving 'bestmove').
To deal with race conditions we also flush the buffer in the GUI thread,
after waiting for thinking to finish, just in case.
  The WB 'easy' and 'hard' command implicitly set the Ponder option,
and this is treated by the hack of injecting fake 'option' commands in the
input stream, which then are subjected to the same buffering scheme.
  Because waiting for thinking termination might now not happen at all
(if we only receive commands that can be handled instantly), a test for
search termination was added immediately after reading the command,
so that every command can now be aware whether thinking is in progress.

UCI2WB.c

index 7f0223e..997423f 100644 (file)
--- a/UCI2WB.c
+++ b/UCI2WB.c
 #define NONE  2\r
 #define ANALYZE 3\r
 \r
-char move[2000][10], checkOptions[8192], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt;\r
+char move[2000][10], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt, searching;\r
 int mps, tc, inc, sTime, depth, myTime, hisTime, stm, computer = NONE, memory, oldMem=0, cores, moveNr, lastDepth, lastScore, startTime, debug, flob;\r
 int statDepth, statScore, statNodes, statTime, currNr, size, collect, nr, sm, inex, on[500], frc, byo = -1, namOpt, comp;\r
-char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000], anaOpt[20];\r
+char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000], anaOpt[20], backLog[10000], checkOptions[8192] = "Ponder";\r
 char board[100];  // XQ board for UCCI\r
 char *nameWord = "name ", *valueWord = "value ", *wTime = "w", *bTime = "b", *wInc = "winc", *bInc = "binc", newGame; // keywords that differ in UCCI\r
 int unit = 1, drawOffer;\r
+volatile int logLen, sentLen;\r
 \r
 FILE *toE, *fromE, *fromF;\r
 int pid;\r
@@ -197,6 +198,17 @@ Analyze(char *val)
     if(*anaOpt) EPRINT((f, "# setoption %s%s %s%s\n", nameWord, anaOpt, valueWord, val));\r
 }\r
 \r
+int\r
+Release()\r
+{   // send setoption commands backlogged during thinking to engine, aborting ponder or analysis search if necessary\r
+    int len = logLen - sentLen, analyse = searching;\r
+    if(len <= 0) return 0;\r
+    StopPonder(pondering | searching); pondering = searching = 0; // force new search if settings change during analysis (multi-PV!)\r
+    fwrite(backLog + sentLen, 1, len, toE); sentLen += len; DPRINT("# release %d\n", len);\r
+    if(ponder && computer == 1 - stm) StartPonder(); // (re)start ponder search\r
+    return analyse; // return 1 if analysis search should be restarted\r
+}\r
+\r
 char *Convert(char *pv)\r
 {   // convert Shogi coordinates to WB\r
     char *p, *q, c;\r
@@ -247,7 +259,7 @@ Engine2GUI()
 \r
     if(fromF = fopen("DefectiveEngineOptions.ini", "r")) printf("# fake engine input\n");\r
     while(1) {\r
-       int i=0, x; char *p, dummy;\r
+       int i=0, x; char *p, dummy, len;\r
 \r
        fflush(stdout); fflush(toE);\r
        while((line[i] = x = GetChar()) != EOF && line[i] != '\n') i++;\r
@@ -265,6 +277,7 @@ Engine2GUI()
            if(strstr(line+9, "(none)") || strstr(line+9, "null") ||\r
               strstr(line+9, "0000")) { printf("%s\n", lastScore < -99999 ? "resign" : "1/2-1/2 {stalemate}"); computer = NONE; }\r
            sscanf(line, "bestmove %s", move[moveNr++]);\r
+           Release(); // send setoption commands that arrived during search\r
            myTime -= (GetTickCount() - startTime)*1.02 + inc; // update own clock, so we can give correct wtime, btime with ponder\r
            if(mps && ((moveNr+1)/2) % mps == 0) myTime += tc; if(sTime) myTime = sTime; // new session or move starts\r
            stm = WHITE+BLACK - stm;\r
@@ -402,7 +415,7 @@ Move4Engine(char *m)
 void\r
 GUI2Engine()\r
 {\r
-    char line[256], command[256], *p, *q, *r, mySide, searching = 0;\r
+    char line[256], command[256], *p, *q, *r, mySide;\r
 \r
     while(1) {\r
        int i, x, difficult, think=0;\r
@@ -426,33 +439,36 @@ GUI2Engine()
        fflush(toE); fflush(stdout);\r
        i = 0; while((x = getchar()) != EOF && (line[i] = x) != '\n') i++;\r
        line[++i] = 0; if(x == EOF) { printf("# EOF\n"); sprintf(line, "quit -1\n"); }\r
-       sscanf(line, "%s", command);\r
+       if(think && !pause) Sync(PAUSE), think = 0, Release(); // if no longer thinking, take dummy pause\r
+       sscanf(line, "%s", command); DPRINT("# '%s' think=%d pause=%d log=%d sent=%d\n", command, think, pause, logLen, sentLen);\r
+       if(!strcmp(command, "easy")) {\r
+           if(*canPonder) ponder = 0, sprintf(command, "option"), sprintf(line, "option %s=0\n", canPonder); else continue;\r
+       }\r
+       else if(!strcmp(command, "hard")) {\r
+           if(*canPonder) ponder = 1, sprintf(command, "option"), sprintf(line, "option %s=1\n", canPonder); else continue;\r
+       }\r
        if(!strcmp(command, "offer")) drawOffer = 1; // backlogged anyway, so this can be done instantly\r
        else if(!strcmp(command, "post"))  post = 1;\r
        else if(!strcmp(command, "nopost"))post = 0;\r
        else if(!strcmp(command, "option")) {\r
-           char name[80], *p;\r
-           if(searching) StopPonder(1), searching = 0; // force new search if settings change during analysis (multi-PV!)\r
+           char *p;\r
+           if(logLen == sentLen) logLen = 0, sentLen = 0; // engine is up to date; reset buffer\r
            if(sscanf(line+7, "UCI2WB debug output=%d", &debug) == 1) ; else\r
            if(sscanf(line+7, "Floating Byoyomi=%d", &flob) == 1) ; else\r
            if(sscanf(line+7, "Byoyomi=%d", &byo) == 1) ; else\r
            if(p = strchr(line, '=')) {\r
                *p++ = 0;\r
                if(strstr(checkOptions, line+7)) sprintf(p, "%s\n", atoi(p) ? "true" : "false");\r
-               EPRINT((f, "# setoption %s%s %s%s", nameWord, line+7, valueWord, p))\r
-           } else EPRINT((f, "# setoption %s%s\n", nameWord, line+7))\r
+               snprintf(backLog+logLen, 9999-logLen, "setoption %s%s %s%s", nameWord, line+7, valueWord, p);\r
+           } else snprintf(backLog+logLen, 9999-logLen, "setoption %s%s\n", nameWord, line+7);\r
+           DPRINT("# backlog: %s", backLog+logLen); logLen += strlen(backLog+logLen);\r
+           if(!think && Release()) break; // break will restart analysis; pondering is restarted by Release itself\r
        }\r
        else if(!strcmp(command, "pause")) {\r
            if(computer == stm) myTime -= GetTickCount() - startTime;\r
            suspended = 1 + pondering; // remember if we were pondering, and stop search ignoring bestmove\r
            StopPonder(pondering || computer == stm);\r
        }\r
-       else if(!strcmp(command, "easy")) {\r
-           if(*canPonder) { ponder = 0; StopPonder(pondering); EPRINT((f, "# setoption %s%s %sfalse\n", nameWord, canPonder, valueWord)) }\r
-       }\r
-       else if(!strcmp(command, "hard")) {\r
-           if(*canPonder) { ponder = 1; EPRINT((f, "# setoption %s%s %strue\n", nameWord, canPonder, valueWord)) StartPonder(); }\r
-       }\r
        else difficult = 1; // difficult command; terminate loop for easy ones\r
        } // next command\r
 \r
@@ -460,10 +476,10 @@ GUI2Engine()
            if(suspended == 2) StartPonder(); // restart interrupted ponder search\r
            suspended = think = 0; continue;  // causes thinking to start in normal way if on move or analyzing\r
        }\r
-       if(think) {      // command arrived during thinking; order abort for 'instant commands'\r
+       if(think) { // command arrived during thinking; order abort for 'instant commands'\r
            if(!strcmp(command, "?") || !strcmp(command, "quit") ||\r
               !strcmp(command, "force") || !strcmp(command, "result")) { EPRINT((f, "# stop\n")); fflush(toE); }\r
-           Sync(PAUSE); // block processing of input during thinking\r
+           Sync(PAUSE); Release(); // block processing of difficult commands during thinking; send backlog left because of race\r
        }\r
        if(!strcmp(command, "new")) {\r
            computer = BLACK; moveNr = 0; depth = -1; move[0][0] = 0;\r