3#include "ProcessWrapper.h"
5#include "EngineEvent.h"
9#include <unordered_map>
10#include <condition_variable>
19 enum SearchStatusCode {
87 std::mutex statusLock;
88 SearchStatusCode status =InitialisingSearch;
89 std::condition_variable var;
96 void set(SearchStatusCode code) {
97 std::unique_lock<std::mutex> guard(statusLock);
112 std::unique_lock<std::mutex> guard(statusLock);
114 SearchStatusCode oldStatus = status;
116 if (status == OnGoing) {
129 std::unique_lock<std::mutex> guard(statusLock);
141 SearchStatusCode
waitFor(
const std::chrono::milliseconds& dur) {
143 std::unique_lock<std::mutex> guard(statusLock);
146 if (status != OnGoing)
150 var.wait_for(guard, dur);
152 if (status == OnGoing)
164 const SearchStatusCode &
operator= (
const SearchStatusCode& value) {
169 bool operator == (
const SearchStatusCode& value) {
170 return value ==
get();
173 bool operator != (
const SearchStatusCode& value) {
174 return value !=
get();
178 template <
class Move>
class EngineInstance;
214 std::shared_ptr<ProcessWrapper> engine;
216 void receiveBestMoveSignal(
const Move* bestMove,
const Move* ponderMove);
217 SearchStatusCode getStatusNoLock();
276 void waitFor(
const std::chrono::milliseconds& time);
289 inline void SearchConnection<Move>::receiveBestMoveSignal(
const Move* bestMove,
const Move* ponderMove){
290 assert(bestMove !=
nullptr);
291 std::lock_guard<std::mutex> guard(lock);
292 status = ResultReady;
294 result.bestMove =
new Move(*bestMove);
295 if (ponderMove) result.ponderMove =
new Move(*ponderMove);
299 inline SearchStatusCode SearchConnection<Move>::getStatusNoLock()
301 if (status == ResultReady || status == Terminated || status == TimedOut)
305 if (!engine->healthCheck()) {
314 inline SearchConnection<Move>::~SearchConnection()
316 destroySearchResultStruct(result);
321 std::lock_guard<std::mutex> guard(lock);
322 return getStatusNoLock();
327 std::lock_guard<std::mutex> guard(lock);
328 if (status != ResultReady)
336 std::lock_guard<std::mutex> guard(lock);
337 SearchStatusCode currentStatus = getStatusNoLock();
338 if (currentStatus == ResultReady || currentStatus == Terminated)
342 engine->getWriter()->write(
"stop\n", 5);
348 std::lock_guard<std::mutex> guard(lock);
349 SearchStatusCode currentStatus = getStatusNoLock();
350 if (currentStatus != OnGoing)
353 engine->getWriter()->write(
"ponderhit\n", 10);
359 SearchStatusCode returnCode = status.waitFor(time);
365 SearchStatusCode returnCode = status.swapIfOngoing(TimedOut);
385 std::shared_ptr<AbstractPipeWriter> writer;
388 void tryWrite(
const std::string& value);
389 void tryWrite(
const char* text);
390 void assertType(
const OptionType&
type)
const;
391 void validateSpinCandidate(
const int32_t& value)
const;
392 void validateComboCandidate(
const std::string& value)
const;
393 void parse(
const std::string& s);
395 int32_t parseInteger(
const std::string& value);
424 EngineOptionProxy(
const Option& option, std::shared_ptr<AbstractPipeWriter> pipeWriter) : value(option), writer(pipeWriter) {};
425 EngineOptionProxy(
const EngineOptionProxy& other) : value(other.value), writer(other.writer) {};
432 inline OptionType
type()
const {
return value.type(); };
439 inline const std::string&
id()
const {
return value.id(); };
468 const std::string&
operator = (
const std::string& value);
495 const int32_t&
operator= (
const int32_t& number);
505 const bool&
operator= (
const bool& value);
529 operator std::string();
580 using Map = std::unordered_map<std::string, EngineOptionProxy>;
581 std::shared_ptr<AbstractPipeWriter> writer;
584 void consume(
const Option& option)
override {
589 using map_iterator = Map::iterator;
595 iterator(
const map_iterator& val) {
598 bool operator != (
const iterator& other) {
599 return _it != other._it;
602 bool operator ==(
const iterator& other) {
603 return !(*
this != other);
606 iterator & operator++() {
618 explicit EngineOptionsMap(
const std::shared_ptr<AbstractPipeWriter>& writer)
623 return proxies.at(
id);
627 return proxies.at(
id);
637 return proxies.count(
id);
645 iterator
begin() {
return iterator(proxies.begin()); }
652 iterator
end() {
return iterator(proxies.end()); }
722 template <
class Move>
725 struct InstancePrivate {
726 std::shared_ptr<SearchConnection<Move>> currentConnection =
nullptr;
727 std::shared_ptr<ProcessWrapper> processWrapper;
728 std::unique_ptr<Logger> logger;
729 bool receivedReadyOk =
false;
730 std::atomic_bool quitCommandSend;
731 std::string name =
"<empty>";
732 std::string author =
"<empty>";
733 std::condition_variable conditional_var;
737 InstancePrivate(std::shared_ptr<ProcessWrapper> engineProcess, std::shared_ptr<
Marschaler<Move>> moveMarshaler, std::shared_ptr<PatternMatcher> moveValidator, std::unique_ptr<Logger> && logger,
EngineInstance<Move>* instance);
738 void sendToEngine(
const std::string& msg);
739 void logFromEngine(
const std::string & msg);
740 void tryReportEngineCrash();
744 std::shared_ptr<InstancePrivate> core;
745 void detachInstance();
748 class _CommandHandler :
public AbstractEngineHandler<Move> {
749 std::shared_ptr<InstancePrivate> core;
751 _CommandHandler(std::shared_ptr<InstancePrivate> core) : core(core) {};
752 void onEngineName(
const std::string& name)
override;
753 void onEngineAuthor(
const std::string& author)
override;
754 void onUCIOK()
override;
755 void onReadyOK()
override;
756 void onBestMove(
const Move& bestMove)
override;
757 void onBestMove(
const Move& bestMove,
const Move& ponderMove)
override;
758 void onInfo(
const std::vector<Info<Move>>& infos)
override;
759 void onCopyProtection(ProcedureStatus status)
override;
760 void onRegistration(ProcedureStatus status)
override;
761 void onOption(
const Option& option)
override;
762 void onError(
const std::string& errorMsg)
override;
765 EngineInstance(std::shared_ptr<ProcessWrapper> engineProcess, std::shared_ptr<
Marschaler<Move>> moveMarshaler, std::shared_ptr<PatternMatcher> moveValidator, std::unique_ptr<Logger> && logger) :
766 options(engineProcess->getWriter())
768 core = std::make_shared<InstancePrivate>(engineProcess, moveMarshaler, moveValidator, std::move(logger),
this);
769 std::shared_ptr<AbstractEngineHandler<Move>> handler = std::static_pointer_cast<AbstractEngineHandler<Move>>(std::make_shared<EngineInstance<Move>::_CommandHandler>(core));
770 auto parser = std::make_shared<UCIParser<Move>>(handler, moveMarshaler, moveValidator);
772 engineProcess->listen([parser, corePtr](std::string line) {
773 corePtr->logFromEngine(line);
774 parser->parseLine(line);
776 [corePtr](){corePtr->tryReportEngineCrash();});
777 core->sendToEngine(
"uci\n");
824 void sync(std::chrono::milliseconds timeout = std::chrono::milliseconds(10000));
843 std::chrono::milliseconds
ping(std::chrono::milliseconds timeout = std::chrono::milliseconds(10000));
882 const std::vector<Move> moves = {});
934 template <
class Move>
935 inline EngineInstance<Move>::InstancePrivate::InstancePrivate(std::shared_ptr<ProcessWrapper> engineProcess, std::shared_ptr<Marschaler<Move>> moveMarshaler, std::shared_ptr<PatternMatcher> moveValidator, std::unique_ptr<Logger> &&logger,
UCILoader::EngineInstance<Move> * instance) :
936 processWrapper(engineProcess), logger(std::move(logger)), quitCommandSend(false), instance(instance) {
939 template <
class Move>
940 inline void EngineInstance<Move>::InstancePrivate::sendToEngine(
const std::string &msg)
942 processWrapper->getWriter()->write(msg.c_str(), msg.size());
946 template <
class Move>
947 inline void EngineInstance<Move>::InstancePrivate::logFromEngine(
const std::string &msg){
951 template <
class Move>
952 inline void EngineInstance<Move>::detachInstance()
954 std::unique_lock<std::mutex> guard(core->lock);
955 core->instance =
nullptr;
958 template <
class Move>
961 std::unique_lock<std::mutex> guard(core->lock);
962 core->receivedReadyOk =
false;
965 core->sendToEngine(
"isready\n");
971 core->conditional_var.wait_for(guard, timeout);
972 if (!core->receivedReadyOk) {
973 core->processWrapper->kill();
977 auto e = NamedEngineEvents::makeSynchronizedEvent();
983 auto start = std::chrono::steady_clock::now();
985 return std::chrono::milliseconds((std::chrono::steady_clock::now() - start).count()/1000000);
991 std::unique_lock<std::mutex> guard(core->lock);
992 return core->currentConnection;
996 const std::vector<Move> moves)
998 std::unique_lock<std::mutex> guard(core->lock);
1000 if (core->currentConnection !=
nullptr)
1003 core->currentConnection = std::make_shared<SearchConnection<Move>>(core->processWrapper);
1005 core->sendToEngine(UciFormatter<Move>::position(pos, moves));
1006 core->sendToEngine(UciFormatter<Move>::go(params));
1007 core->currentConnection->status.set(OnGoing);
1009 auto event = NamedEngineEvents::makeSearchStartedEvent();
1011 return core->currentConnection;
1013 template<
class Move>
1016 std::unique_lock<std::mutex> guard(core->lock);
1020 template<
class Move>
1023 std::unique_lock<std::mutex> guard(core->lock);
1024 return core->author;
1027 template<
class Move>
1030 std::unique_lock<std::mutex> guard(core->lock);
1032 if (core->quitCommandSend)
1035 core->quitCommandSend =
true;
1037 core->sendToEngine(
"quit\n");
1046 while (core->processWrapper->healthCheck()) {
1048 if (counter == 10) {
1049 core->processWrapper->kill();
1052 std::this_thread::sleep_for(std::chrono::milliseconds(100));
1057 template<
class Move>
1059 if (quitCommandSend ==
false) {
1060 auto event = NamedEngineEvents::makeEngineCrashedEvent();
1065 template <
class Move>
1066 inline void EngineInstance<Move>::InstancePrivate::emit(
const EngineEvent *event) {
1068 instance->emit(event);
1071 template<
class Move>
1073 return core->processWrapper->healthCheck();
1076 template<
class Move>
1078 core->sendToEngine(
"ucinewgame\n");
1081 template<
class Move>
1084 std::unique_lock<std::mutex> guard(core->lock);
1088 template<
class Move>
1089 inline void EngineInstance<Move>::_CommandHandler::onEngineAuthor(
const std::string& author)
1091 std::unique_lock<std::mutex> guard(core->lock);
1092 core->author = author;
1095 template<
class Move>
1096 inline void EngineInstance<Move>::_CommandHandler::onUCIOK()
1101 template<
class Move>
1102 inline void EngineInstance<Move>::_CommandHandler::onReadyOK()
1104 std::unique_lock<std::mutex> guard(core->lock);
1105 core->receivedReadyOk =
true;
1106 core->conditional_var.notify_all();
1109 template<
class Move>
1110 inline void EngineInstance<Move>::_CommandHandler::onBestMove(
const Move& bestMove)
1112 std::unique_lock<std::mutex> guard(core->lock);
1113 static auto searchCompletedEvent = NamedEngineEvents::makeSearchCompletedEvent();
1115 if (core->currentConnection !=
nullptr) {
1116 core->currentConnection->receiveBestMoveSignal(&bestMove,
nullptr);
1117 core->currentConnection =
nullptr;
1118 core->emit(&searchCompletedEvent);
1122 template<
class Move>
1123 inline void EngineInstance<Move>::_CommandHandler::onBestMove(
const Move& bestMove,
const Move& ponderMove)
1125 std::unique_lock<std::mutex> guard(core->lock);
1126 static auto searchCompletedEvent = NamedEngineEvents::makeSearchCompletedEvent();
1127 if (core->currentConnection !=
nullptr) {
1128 core->currentConnection->receiveBestMoveSignal(&bestMove, &ponderMove);
1129 core->currentConnection =
nullptr;
1130 core->emit(&searchCompletedEvent);
1134 template<
class Move>
1135 inline void EngineInstance<Move>::_CommandHandler::onInfo(
const std::vector<Info<Move>>& infos)
1137 std::unique_lock<std::mutex>guard(core->lock);
1139 auto clampEvent = UCILoader::NamedEngineEvents::makeInfoClampEvent<Move>(infos);
1140 core->emit(&clampEvent);
1141 for (
auto& info : infos) {
1142 auto infoEvent = UCILoader::NamedEngineEvents::makeInfoEvent<Move>(info);
1143 core->emit(&infoEvent);
1147 template<
class Move>
1148 inline void EngineInstance<Move>::_CommandHandler::onCopyProtection(ProcedureStatus status)
1152 template<
class Move>
1153 inline void EngineInstance<Move>::_CommandHandler::onRegistration(ProcedureStatus status)
1157 template<
class Move>
1158 inline void EngineInstance<Move>::_CommandHandler::onOption(
const Option& option)
1160 std::unique_lock<std::mutex> guard(core->lock);
1161 if (core->instance){
1162 IOptionConsumer* consumer = (IOptionConsumer*)(&core->instance->options);
1163 consumer->consume(option);
1168 template<
class Move>
1169 inline void EngineInstance<Move>::_CommandHandler::onError(
const std::string& errorMsg)
1210 template <
class Move>
1212 std::shared_ptr<PatternMatcher> moveValidator;
1213 std::shared_ptr<Marschaler<Move>> moveMarshaler;
1222 moveValidator(validator), moveMarshaler(marschaler) {};
1271 template<
class Move>
1276 template<
class Move>
1279 std::shared_ptr<ProcessWrapper> proces(engineProcess);
Base class for events emitted by an EngineInstance.
Definition: EngineEvent.h:42
Factory builder for creating EngineInstance objects.
Definition: EngineConnection.h:1211
EngineInstanceBuilder(std::shared_ptr< PatternMatcher > validator, std::shared_ptr< Marschaler< Move > > marschaler)
Constructor for the builder.
Definition: EngineConnection.h:1221
EngineInstance< Move > * build(ProcessWrapper *engineProcess)
Build an EngineInstance with default logging (no-op logger).
Definition: EngineConnection.h:1272
Exception thrown when attempting to start a search while one is already in progress.
Definition: EngineConnection.h:799
Exception thrown when a sync() or ping() operation times out.
Definition: EngineConnection.h:791
Top-level interface for managing and interacting with a UCI chess engine.
Definition: EngineConnection.h:723
std::string getName()
Get the engine's name as declared via the id name command.
Definition: EngineConnection.h:1014
bool healthCheck()
Check the health status of the underlying engine process.
Definition: EngineConnection.h:1072
std::shared_ptr< SearchConnection< Move > > getCurrentSearch()
Get the currently active search connection.
Definition: EngineConnection.h:989
void ucinewgame()
Send the ucinewgame command to the engine.
Definition: EngineConnection.h:1077
void sync(std::chrono::milliseconds timeout=std::chrono::milliseconds(10000))
Synchronize engine state with local representation.
Definition: EngineConnection.h:959
std::chrono::milliseconds ping(std::chrono::milliseconds timeout=std::chrono::milliseconds(10000))
Measure engine latency by performing a ping operation.
Definition: EngineConnection.h:981
void quit()
Manually terminate the engine process.
Definition: EngineConnection.h:1028
std::string getAuthor()
Get the engine author's name as declared via the id author command.
Definition: EngineConnection.h:1021
std::shared_ptr< SearchConnection< Move > > search(const GoParams< Move > ¶ms, const PositionFormatter &pos, const std::vector< Move > moves={})
Start a new search operation with specified parameters.
Definition: EngineConnection.h:995
EngineOptionsMap options
Map of available engine options.
Definition: EngineConnection.h:806
Exception thrown when setting an option to an unsupported value.
Definition: EngineConnection.h:415
Exception thrown when parsing fails for a miscellaneus reason.
Definition: EngineConnection.h:421
Exception thrown when an operation is incompatible with the option type.
Definition: EngineConnection.h:403
Proxy object for interacting with a UCI engine option.
Definition: EngineConnection.h:384
OptionType type() const
Get the option type.
Definition: EngineConnection.h:432
const Option & getAsOption() const
Get the full Option object with all metadata.
Definition: EngineConnection.h:447
const std::string & operator=(const std::string &value)
Set option value from a string.
Definition: EngineConnection.cpp:94
void click()
Activate a button option.
Definition: EngineConnection.cpp:88
const std::string & id() const
Get the option name.
Definition: EngineConnection.h:439
Container and iterator for engine option proxies.
Definition: EngineConnection.h:578
bool contains(const std::string &id) const
Check if the engine has an option with the specified name.
Definition: EngineConnection.h:636
const EngineOptionProxy & operator[](const std::string &id) const
Access option by name using bracket notation (const version).
Definition: EngineConnection.h:672
iterator end()
Get iterator to one past the last option.
Definition: EngineConnection.h:652
EngineOptionProxy & operator[](const std::string &id)
Access option by name using bracket notation.
Definition: EngineConnection.h:661
iterator begin()
Get iterator to the first option.
Definition: EngineConnection.h:645
Base class for objects that emit engine events.
Definition: EngineEvent.h:494
Internal interface for consuming option updates from the engine.
Definition: EngineConnection.h:540
virtual void consume(const Option &o)=0
Process a newly discovered engine option.
Builder class for constructing and composing loggers with traits.
Definition: Logger.h:158
std::unique_ptr< Logger > build()
Build and return the final logger instance.
Definition: Logger.cpp:227
@ ToEngine
Message sent from the application to the chess engine.
Definition: Logger.h:38
@ FromParser
Message generated or parsed by the UCI parser.
Definition: Logger.h:40
@ FromEngine
Message received from the chess engine.
Definition: Logger.h:39
Exception thrown when a pipe operation fails due to pipe closure.
Definition: AbstractPipe.h:21
Cross-platform wrapper for managing a chess engine process.
Definition: ProcessWrapper.h:88
Exception thrown when attempting to retrieve results before search completes.
Definition: EngineConnection.h:204
Manages and controls an ongoing chess engine search operation.
Definition: EngineConnection.h:199
void ponderhit()
Send a ponderhit command to the engine.
Definition: EngineConnection.h:346
SearchConnection(std::shared_ptr< ProcessWrapper > engine)
Constructor for SearchConnection.
Definition: EngineConnection.h:226
void stop()
Send a stop command to halt the engine's search.
Definition: EngineConnection.h:334
void waitFor(const std::chrono::milliseconds &time)
Block until the search completes or timeout expires.
Definition: EngineConnection.h:357
const SearchResult< Move > & getResult()
Retrieve the search result.
Definition: EngineConnection.h:325
SearchStatusCode getStatus()
Get the current status of the search operation.
Definition: EngineConnection.h:319
void timeOutIfNotFinished()
Set status to TimedOut if search is still ongoing.
Definition: EngineConnection.h:363
Thread-safe wrapper for managing SearchStatusCode state.
Definition: EngineConnection.h:86
void set(SearchStatusCode code)
Set the search status to the specified code.
Definition: EngineConnection.h:96
const SearchStatusCode & operator=(const SearchStatusCode &value)
Assignment operator for convenient status setting.
Definition: EngineConnection.h:164
SearchStatusCode waitFor(const std::chrono::milliseconds &dur)
Block until the search status changes or timeout expires.
Definition: EngineConnection.h:141
SearchStatusCode get()
Get the current search status.
Definition: EngineConnection.h:128
SearchStatusCode swapIfOngoing(SearchStatusCode newStatus)
Atomically swap status if current status is OnGoing.
Definition: EngineConnection.h:111
LoggerBuilder toNoting()
Create a logger that discards all messages.
Definition: Logger.cpp:171
Container for the result of a chess engine search operation.
Definition: EngineConnection.h:49
Move * ponderMove
[Optional] Move the engine wishes to ponder on, or nullptr if not provided by the engine
Definition: EngineConnection.h:53
Move * bestMove
The best move chosen by the engine for the given position.
Definition: EngineConnection.h:51