+class Worker;
+
+// Null Object Pattern, implement a common interface for the SearchManagers.
+// A Null Object will be given to non-mainthread workers.
+class ISearchManager {
+ public:
+ virtual ~ISearchManager() {}
+ virtual void check_time(Search::Worker&) = 0;
+};
+
+struct InfoShort {
+ int depth;
+ Score score;
+};
+
+struct InfoFull: InfoShort {
+ int selDepth;
+ size_t multiPV;
+ std::string_view wdl;
+ std::string_view bound;
+ size_t timeMs;
+ size_t nodes;
+ size_t nps;
+ size_t tbHits;
+ std::string_view pv;
+ int hashfull;
+};
+
+struct InfoIteration {
+ int depth;
+ std::string_view currmove;
+ size_t currmovenumber;
+};
+
+// Skill structure is used to implement strength limit. If we have a UCI_Elo,
+// we convert it to an appropriate skill level, anchored to the Stash engine.
+// This method is based on a fit of the Elo results for games played between
+// Stockfish at various skill levels and various versions of the Stash engine.
+// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately
+// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2
+struct Skill {
+ // Lowest and highest Elo ratings used in the skill level calculation
+ constexpr static int LowestElo = 1320;
+ constexpr static int HighestElo = 3190;
+
+ Skill(int skill_level, int uci_elo) {
+ if (uci_elo)
+ {
+ double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo);
+ level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
+ }
+ else
+ level = double(skill_level);
+ }
+ bool enabled() const { return level < 20.0; }
+ bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
+ Move pick_best(const RootMoves&, size_t multiPV);
+
+ double level;
+ Move best = Move::none();
+};
+
+// SearchManager manages the search from the main thread. It is responsible for
+// keeping track of the time, and storing data strictly related to the main thread.
+class SearchManager: public ISearchManager {
+ public:
+ using UpdateShort = std::function<void(const InfoShort&)>;
+ using UpdateFull = std::function<void(const InfoFull&)>;
+ using UpdateIter = std::function<void(const InfoIteration&)>;
+ using UpdateBestmove = std::function<void(std::string_view, std::string_view)>;
+
+ struct UpdateContext {
+ UpdateShort onUpdateNoMoves;
+ UpdateFull onUpdateFull;
+ UpdateIter onIter;
+ UpdateBestmove onBestmove;
+ };
+
+
+ SearchManager(const UpdateContext& updateContext) :
+ updates(updateContext) {}
+
+ void check_time(Search::Worker& worker) override;
+
+ void pv(Search::Worker& worker,
+ const ThreadPool& threads,
+ const TranspositionTable& tt,
+ Depth depth);
+
+ Stockfish::TimeManagement tm;
+ double originalTimeAdjust;
+ int callsCnt;
+ std::atomic_bool ponder;
+
+ std::array<Value, 4> iterValue;
+ double previousTimeReduction;
+ Value bestPreviousScore;
+ Value bestPreviousAverageScore;
+ bool stopOnPonderhit;
+
+ size_t id;
+
+ const UpdateContext& updates;
+};
+
+class NullSearchManager: public ISearchManager {
+ public:
+ void check_time(Search::Worker&) override {}
+};
+
+
+// Search::Worker is the class that does the actual search.
+// It is instantiated once per thread, and it is responsible for keeping track
+// of the search history, and storing data required for the search.
+class Worker {
+ public:
+ Worker(SharedState&, std::unique_ptr<ISearchManager>, size_t, NumaReplicatedAccessToken);
+
+ // Called at instantiation to initialize reductions tables.
+ // Reset histories, usually before a new game.
+ void clear();
+
+ // Called when the program receives the UCI 'go' command.
+ // It searches from the root position and outputs the "bestmove".
+ void start_searching();
+
+ bool is_mainthread() const { return threadIdx == 0; }
+
+ void ensure_network_replicated();
+
+ // Public because they need to be updatable by the stats
+ ButterflyHistory mainHistory;
+ CapturePieceToHistory captureHistory;
+ ContinuationHistory continuationHistory[2][2];
+ PawnHistory pawnHistory;
+ PawnCorrectionHistory pawnCorrectionHistory;
+ MaterialCorrectionHistory materialCorrectionHistory;
+
+ private:
+ void iterative_deepening();
+
+ // This is the main search function, for both PV and non-PV nodes
+ template<NodeType nodeType>
+ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
+
+ // Quiescence search function, which is called by the main search
+ template<NodeType nodeType>
+ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta);
+
+ Depth reduction(bool i, Depth d, int mn, int delta) const;
+
+ // Pointer to the search manager, only allowed to be called by the main thread
+ SearchManager* main_manager() const {
+ assert(threadIdx == 0);
+ return static_cast<SearchManager*>(manager.get());
+ }
+
+ TimePoint elapsed() const;
+ TimePoint elapsed_time() const;
+
+ LimitsType limits;
+
+ size_t pvIdx, pvLast;
+ std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
+ int selDepth, nmpMinPly;
+
+ Value optimism[COLOR_NB];
+
+ Position rootPos;
+ StateInfo rootState;
+ RootMoves rootMoves;
+ Depth rootDepth, completedDepth;
+ Value rootDelta;
+
+ size_t threadIdx;
+ NumaReplicatedAccessToken numaAccessToken;
+
+ // Reductions lookup table initialized at startup
+ std::array<int, MAX_MOVES> reductions; // [depth or moveNumber]
+
+ // The main thread has a SearchManager, the others have a NullSearchManager
+ std::unique_ptr<ISearchManager> manager;
+
+ Tablebases::Config tbConfig;
+
+ const OptionsMap& options;
+ ThreadPool& threads;
+ TranspositionTable& tt;
+ const LazyNumaReplicated<Eval::NNUE::Networks>& networks;
+
+ // Used by NNUE
+ Eval::NNUE::AccumulatorCaches refreshTable;
+
+ friend class Stockfish::ThreadPool;
+ friend class SearchManager;
+};