Let engine thread parse difficult commands
authorH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 8 Dec 2018 11:00:48 +0000 (12:00 +0100)
committerH.G.Muller <hgm@hgm-xboard.(none)>
Sat, 8 Dec 2018 11:42:56 +0000 (12:42 +0100)
The design of the adapter is completely changed: the engine thread is
made responsible for parsing and execution of all commands that cannot
be processed during a search. To this end it alternates between reading
from the engine (while the engine is searching, or should respond to a
'uci' or 'isready' query) and from the command queue (which is emptied
before we dedicate to relaying ponder output). Searches are terminated
by the GUI thread when a command arrives that would interrupt the search,
so that the engine thread will start looking at the command queue again.
  Syncing threads to wait for an engine reply is replaced by just doing
a (blocking) read for engine output in the same threat, and having the
processing routine return once the answer has arrived (and is processed).
Instead the Sync pipe is now used to block reading of the command queue
when this is empty.
  This change breaks several of the less elementary commands.

Fix command queue

UCI2WB.c

index 8de43f5..135b3a7 100644 (file)
--- a/UCI2WB.c
+++ b/UCI2WB.c
@@ -42,7 +42,7 @@
 #define NONE  2\r
 #define ANALYZE 3\r
 \r
-char move[2000][10], iniPos[256], hashOpt[20], pause, suspended, ponder, post, hasHash, c, sc='c', suffix[81], varOpt, searching, *binary;\r
+char move[2000][10], iniPos[256], hashOpt[20], suspended, ponder, post, hasHash, c, sc='c', suffix[81], varOpt, searching, *binary;\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], backLog[10000], checkOptions[8192] = "Ponder";\r
@@ -164,9 +164,7 @@ void
 StopPonder(int pondering)\r
 {\r
        if(!pondering) return;\r
-       pause = 1;\r
        EPRINT((f, "# stop\n")) fflush(toE); // note: 'pondering' remains set until engine acknowledges 'stop' with 'bestmove'\r
-       Sync(PAUSE); // wait for engine to acknowledge 'stop' with 'bestmove'.\r
 }\r
 \r
 void\r
@@ -264,8 +262,8 @@ HandleEngineOutput()
        DPRINT("# engine said: %s", line), fflush(stdout);\r
        if(sscanf(line, "%s", command) != 1) continue;\r
        if(!strcmp(command, "bestmove")) {\r
-           if(pause == 1) { searching = pause = 0; Sync(WAKEUP); continue; } // bestmove was reply to ponder miss or analysis result; ignore.\r
-           else if(searching == 1) { searching = 0; printf("%d 0 0 0 UCI violation! Engine moves during ponder\n", lastDepth+1); continue; } // ignore ponder search\r
+           if(searching == 1) { searching = 0; printf("%d 0 0 0 UCI violation! Engine moves during ponder\n", lastDepth+1); return; } // ignore ponder search\r
+           else if(searching != 3) { searching = 0; return; } // ponder miss or analysis result; ignore.\r
            // move was a move to be played\r
            if(p = strstr(line+8, " draw")) *p = 0, printf("offer draw\n"); // UCCI\r
            if(strstr(line+9, "resign")) { printf("resign\n"); computer = NONE; }\r
@@ -289,8 +287,8 @@ HandleEngineOutput()
            Move4GUI(line+9);\r
            printf("move %s\n", line+9); // send move to GUI\r
             if(move[moveNr][0]) printf("Hint: %s\n", move[moveNr]);\r
-           if(pause) { pause = 0; Sync(WAKEUP); } // release commands that came in during think\r
            if(lastScore == 100001 && iniPos[0] != 'f') { printf("%s {mate}\n", stm == BLACK ? "1-0" : "0-1"); computer = NONE; }\r
+           return;\r
        }\r
        else if(!strcmp(command, "info")) {\r
            int d=0, s=0, t=(GetTickCount() - startTime)/10, n=1;\r
@@ -395,7 +393,7 @@ HandleEngineOutput()
            if(sscanf(line, "id name %[^\n]", name) == 1) printf("feature myname=\"%s (U%cI2WB)\"\n", name, sc-32);\r
            if(sscanf(line, "id version %[^\n]", version) == 1 && *name) printf("feature myname=\"%s %s (U%cI2WB)\"\n", name, version, sc-32);\r
        }\r
-       else if(!strcmp(command, "readyok")) { pause = 0; Sync(WAKEUP); } // resume processing of GUI commands\r
+       else if(!strcmp(command, "readyok")) return; // resume processing of GUI commands\r
        else if(sc == 'x'&& !strcmp(command, "ucciok") || sscanf(command, "u%ciok", &c)==1 && c==sc) {\r
            char *p = varList, *q = varList;\r
            while(*q && *q != '\n')  if(!strncmp(q, " var ", 5)) *p++ = ',', q +=5; // replace var keywords by commas\r
@@ -406,7 +404,7 @@ HandleEngineOutput()
            if(*egts) printf("feature egt=\"%s\"\n", egts+1);\r
            printf("feature smp=1 memory=%d done=1\n", hasHash);\r
            if(unit == 2) { unit = 1; EPRINT((f, "# setoption usemillisec true\n")) }\r
-           Sync(WAKEUP); // done with options\r
+           return; // done with options\r
        }\r
     }\r
 }\r
@@ -431,7 +429,7 @@ Move4Engine(char *m)
 \r
 int DoCommand ();\r
 char mySide;\r
-char queue[10000], *qStart, *qEnd;\r
+volatile char queue[10000], *qStart, *qEnd;\r
 \r
 void\r
 LaunchSearch()\r
@@ -450,7 +448,7 @@ LaunchSearch()
                    for(i=1; i<nr; i++) if(on[i]) EPRINT((f, " %s", moveMap[i]))\r
                }\r
                EPRINT((f, "\n")) searching = 2; // suppresses spurious commands during analysis starting new searches\r
-           } else pause = 2, searching = 3, StartSearch(""); // request suspending of input processing while thinking\r
+           } else searching = 3, StartSearch(""); // request suspending of input processing while thinking\r
        } else if(ponderAlways && computer == NONE) move[moveNr][0] = 0, StartPonder(moveNr-1);\r
 }\r
 \r
@@ -465,8 +463,7 @@ GUI2Engine()
        for(difficult=0; !difficult; ) { // read and handle commands that can (or must) be handled during thinking\r
        fflush(toE); fflush(stdout);\r
        if(!ReadLine(stdin, line)) printf("# EOF\n"), sprintf(line, "quit -1\n");\r
-       if(searching == 3 && !pause) Sync(PAUSE), searching = 0, Release(); // if no longer thinking, take dummy pause\r
-       sscanf(line, "%s", command); DPRINT("# '%s' searching=%d pause=%d log=%d sent=%d\n", command, searching, pause, logLen, sentLen);\r
+       sscanf(line, "%s", command); DPRINT("# '%s' searching=%d log=%d sent=%d\n", command, searching, logLen, sentLen);\r
        if(!strcmp(command, "usermove")) { difficult--; break; } // for efficiency during game play, moves, time & otim are tried first\r
        else if(!strcmp(command, "time"))   sscanf(line+4, "%d", &myTime),  myTime  = (10*myTime)/unit;\r
        else if(!strcmp(command, "otim"))   sscanf(line+4, "%d", &hisTime), hisTime = (10*hisTime)/unit;\r
@@ -510,17 +507,17 @@ GUI2Engine()
            Move4Engine(command);\r
            stm = WHITE+BLACK - stm; collect = (computer == ANALYZE); sm = 0;\r
            // when pondering we either continue the ponder search as normal search, or abort it\r
-           if(searching) { // move cannot come during think\r
+           if(searching) { // move cannot come during think, so we are pondering or analysing\r
                if(searching == 1 && !strcmp(command, move[moveNr])) { // ponder hit\r
                    char *draw = drawOffer ? " draw" : ""; drawOffer = 0;\r
-                   searching = 0; pause = 2; moveNr++; startTime = GetTickCount(); // clock starts running now\r
+                   searching = 3; moveNr++; startTime = GetTickCount(); // clock starts running now\r
                    EPRINT((f, "# ponderhit%s\n", draw)) fflush(toE); fflush(stdout);\r
-                   searching = 3; // request blocking input during thinking\r
                    continue;\r
                }\r
-               StopPonder(1); searching = 0;\r
+               searching = 0; StopPonder(1);\r
            }\r
            strcpy(move[moveNr++], command); // possibly overwrites ponder move\r
+           *qEnd++ = '\n'; Sync(WAKEUP);    // make sure engine thread considers starting a search\r
        } else\r
        if(!strcmp(command, "resume")) {\r
            searching = 0;\r
@@ -530,15 +527,14 @@ GUI2Engine()
       {\r
        if(searching == 3) { // 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); Release(); // block processing of difficult commands during thinking; send backlog left because of race\r
-       }\r
+              !strcmp(command, "force") || !strcmp(command, "result")) StopPonder(1);\r
+       } else if(searching) searching = 0, StopPonder(1); // always abort pondering or analysis\r
+\r
+       // queue command for execution by engine thread\r
        if(qStart == qEnd) qStart = qEnd = queue;\r
        p = line; while(qEnd < queue+10000 && (*qEnd++ = *p++) != '\n') {}\r
-       if(DoCommand()) continue;\r
+       Sync(WAKEUP);\r
       }\r
-\r
-       LaunchSearch(); // start a search if we need one\r
     }\r
 }\r
 \r
@@ -549,6 +545,7 @@ DoCommand ()
     int i;\r
 \r
     p=line; while(qStart < qEnd && (*p++ = *qStart++) != '\n') {} *p = 0;\r
+    if(line[0] == '\n') return 0;\r
     sscanf(line, "%s", command);\r
 \r
        if(!strcmp(command, "new")) {\r
@@ -559,9 +556,8 @@ DoCommand ()
            // we can set other options here\r
            if(sc == 'x') { if(newGame) EPRINT((f, "# setoption newgame\n")) } else // optional in UCCI\r
            if(varOpt) EPRINT((f, "# setoption name UCI_Variant value chess\n"))\r
-           pause = 1; // wait for option settings to take effect\r
            EPRINT((f, "# isready\n")) fflush(toE);\r
-           Sync(PAUSE); // wait for readyok\r
+           HandleEngineOutput(); // wait for readyok\r
            EPRINT((f, "# u%cinewgame\n", sc)) fflush(toE);\r
        }\r
        else if(!strcmp(command, "level")) {\r
@@ -577,7 +573,7 @@ DoCommand ()
            printf("feature option=\"ponder always -check %d\"\n", ponderAlways);\r
            if(sc == 's') printf("feature option=\"Floating Byoyomi -check %d\"\nfeature option=\"Byoyomi -spin %d -1 1000\"\n", flob, byo);\r
            EPRINT((f, sc == 'x' ? "# ucci\n" : "# u%ci\n", sc)) fflush(toE); // prompt UCI engine for options\r
-           Sync(PAUSE); // wait for uciok\r
+           HandleEngineOutput(); // wait for uciok\r
        }\r
        else if(!strcmp(command, "setboard")) {\r
                stm = (strstr(line+9, " b ") ? BLACK : WHITE);\r
@@ -594,7 +590,7 @@ DoCommand ()
        else if(!strcmp(command, "variant")) {\r
                if(varOpt) {\r
                    EPRINT((f, "# setoption name UCI_Variant value %sucinewgame\nisready\n", line+8))\r
-                   fflush(toE); Sync(PAUSE);\r
+                   fflush(toE); HandleEngineOutput(); // wait for readyok\r
                }\r
                if(!strcmp(line+8, "shogi\n")) size = 9, strcpy(iniPos, "position startpos");\r
                if(!strcmp(line+8, "5x5+5_shogi\n")) size = 5, strcpy(iniPos, "position startpos");\r
@@ -602,7 +598,6 @@ DoCommand ()
                if(!strcmp(line+8, "fischerandom\n")) { frc |= 1; if(frc > 0) EPRINT((f, "# setoption name UCI_Chess960 value true\n")) }\r
        }\r
        else if(!strcmp(command, "undo") && (i=1) || !strcmp(command, "remove") && (i=2)) {\r
-           if(searching) StopPonder(1), searching = 0;\r
            moveNr = moveNr > i ? moveNr - i : 0; collect = (computer == ANALYZE); sm = 0;\r
        }\r
        else if(!strcmp(command, ".")) {\r
@@ -614,12 +609,11 @@ DoCommand ()
            inex = 1; line[strlen(line)-1] = sm = 0; // strip LF and clear sm flag\r
            for(i=1; i<nr; i++) { if(!strcmp(line+8, moveMap[i]) || all) on[i] = in; sm |= on[i]+1; } // sm: 2 = enabled, 1 = disabled\r
            if(!(sm & 2)) return 1; // no moves enabled; continue current search\r
-           if(computer == ANALYZE) StopPonder(1), searching = 0; // abort old analysis\r
        }\r
        else if(!strcmp(command, "xboard")) ;\r
        else if(!strcmp(command, "analyze"))computer = ANALYZE, collect = 1, sm = 0, Analyze("true");\r
-       else if(!strcmp(command, "exit"))   computer = NONE, StopPonder(1), searching = 0, Analyze("false");\r
-       else if(!strcmp(command, "force"))  computer = NONE, StopPonder(searching == 1);\r
+       else if(!strcmp(command, "exit"))   computer = NONE, Analyze("false");\r
+       else if(!strcmp(command, "force"))  computer = NONE;\r
        else if(!strcmp(command, "go"))     computer = stm;\r
        else if(!strcmp(command, "ping"))   { /* static int done; if(!done) pause = 1, fprintf(toE, "isready\n"), fflush(toE), printf("# send isready\n"), fflush(stdout), Sync(PAUSE); done = 1;*/ printf("po%s", line+2); }\r
        else if(!strcmp(command, "memory")) sscanf(line, "memory %d", &memory);\r
@@ -646,7 +640,12 @@ void *
 Engine2GUI()\r
 {\r
     if(fromF = fopen("DefectiveEngineOptions.ini", "r")) printf("# fake engine input\n");\r
-    HandleEngineOutput();\r
+    while(1) {\r
+       if(searching > 1) HandleEngineOutput();  // this could leave us (or fall through) pondering\r
+       while(qStart == qEnd && searching) HandleEngineOutput(); // relay ponder output until command arrives\r
+       Sync(PAUSE); // possibly wait for command silently if engine is idle\r
+       if(!DoCommand()) LaunchSearch();\r
+    }\r
 }\r
 \r
 int\r