Specs vs. doxygen. The per-method contracts of
SolverContextand its facades are documented inline insolver_context.hpp. This spec records why the context exists and the lifecycle/ownership invariants that span the whole class.
SolverContext is the instance-scoped home for everything a solve needs that was
historically global mutable state: the per-thread ThreadData, the transposition
table, search state, move generation, and logging/stats. It exists so a caller can
create one context at the top of a call stack, drive multiple solves through it,
and reuse the (expensive) transposition table across them — instead of relying on
process-global state that forced serialized, non-reentrant use. It is the object
the modern C++ API (dds_api.hpp) and its C shim (dds_c_api.h) hand around as
the opaque handle. See dds-public-api.
Method signatures live in the header's doxygen; these are the whole-context guarantees.
- One context per thread.
SolverContextis not thread-safe. Each thread that solves owns its own context (and thus its ownThreadDataand TT). ThreadDataisshared_ptr-owned. A context either creates and owns itsThreadData(SolverContext(SolverConfig)) or wraps an externally-owned one (SolverContext(shared_ptr<ThreadData>, cfg)). Raw-ThreadData*constructors were removed in the ownership migration; non-owning wrappers use a no-op-deletershared_ptr. The destructor closes debug files only when it is the last holder of thatThreadData.- The TT is owned per context, created lazily. Each context's
SearchContextowns itsTransTablevia aunique_ptr, allocated on first access. There is no global TT registry and noThreadData-owned TT — TT reuse comes from reusing the same context across solves, not from a shared global. See transposition-table. - TT configuration is
SolverConfig+ optional env overrides.SolverConfigcarriestt_kind_(TTKind::{Small,Large,Pattern}, defaultPattern) and default/max MB.configure_tt(kind, defMB, maxMB)persists a new config and applies it to an existing TT (resize in place, or recreate if the effective kind changes). Non-positive limits are resolved the same way in bothconfigure_ttand lazy creation, one value at a time: an unset maximum becomes the built-in limit and an unset default becomes the built-in default capped by the maximum, so a maximum-only configuration keeps its cap and a live table is never handed a zero maximum. Env overrides:DDS_TT_KIND=small|large|patternreplaces the configured kind (at creation and inconfigure_tt's recreate decision); when > 0,DDS_TT_LIMIT_MBcaps the maximum (at creation and on everyconfigure_tt), andDDS_TT_DEFAULT_MBreplaces the configured default MB only when a table is created (lazily, or onconfigure_tt's recreate path) — an in-place resize applies the explicitdefMBas given. In both paths the maximum is then floored to the effective default (maxMB = max(maxMB, defMB)), soDDS_TT_LIMIT_MBcannot push the maximum below the default: to cap below the built-in default, lower the default too (DDS_TT_DEFAULT_MBorconfigure_tt). - Explicit, tiered reset hooks (no-ops when no TT exists yet):
reset_for_solve()clears a subset of search state and resets TT memory (ResetReason::FreeMemory) while preserving the allocation for reuse;reset_best_moves_lite()clears only best-move ranks (hot per-iteration path);clear_tt()disposes the TT instance;dispose_trans_table()destroys the TT immediately. clear_tt()leaves the context reusable. It disposes the TT instance rather than callingreturn_all_memory()on it, sott_becomes null and the nextSearchContext::trans_table()rebuilds an empty table lazily. The configured kind and memory limits survive, because they live inSolverContext::cfg_and not in the TT instance. Callingreturn_all_memory()while keeping the object was the earlier behavior and was unsafe: it left a husk whose pool pointers dangled, whichtrans_table()handed straight back because it only checks whethertt_is non-null — the nextlookup/addread freed memory.clear_tt()anddispose_trans_table()now differ only in their log/stats trace. Guarded by//library/tests:dds_c_api_test(DdsCApiTtConfiguration.ClearTtThenSolveOnDefaultTt). Disposing also forgets the deal the thread remembers (ThreadData::suit), so the next solve — even of the same cards — is treated as a new deal and runsSetDealTables(), whichinit()s the replacement table; otherwise a table recreated between two solves of one deal would never see the deal (inert onTransTableP). Guarded byConfigureTtApiTest.ATableRecreatedBetweenSolvesOfTheSameDealIsInitializedAgain.- Hot-path facades are value-typed and inline-friendly, with different holds.
MoveGenContextholds a rawThreadData*somove_gen()can return a value-typed facade without an atomicshared_ptrbump on every call.SearchContextholdsshared_ptr<ThreadData>(and owns the TT); its trivial accessors are header-defined so call sites inline direct field accesses.UtilitiesContextwraps adds::Utilities*, notThreadData. Treat the header-inlinedSearchContextaccessors as a performance contract. - Log/stats are build-time variants.
solver_context(plain),solver_context_log(DDS_UTILITIES_LOG), andsolver_context_stats(DDS_UTILITIES_STATS) compile the same source with different defines; TT lifecycle events emit log entries / bump counters only in those variants. Link at most one variant into a binary. See system-concurrency and build-system.
library/src/solver_context/solver_context.hpp—SolverContext,SolverConfig,TTKind, and theSearchContext/MoveGenContext/UtilitiesContextfacades. Doxygen documents each method.library/src/solver_context/solver_context.cpp— out-of-line construction, TT lifecycle, and reset implementations.library/src/solver_context_adapter.cpp—solve_board(SolverContext&, …)adapter that drives the internal solver from a context.- Build targets:
//library/src/solver_context:{solver_context,solver_context_log, solver_context_stats}.
- Not thread-safe by design — concurrency is achieved with one context per worker, coordinated by system-concurrency, not by locking a shared context.
- The context does not persist across processes; the TT is in-memory only.
- The class header still describes itself as "scaffolding … no-behavior-change
adapter" from the migration; the instance-scoped ownership model above is the
current, authoritative behavior. (A stale comment in
solver_context_adapter.cppstill refers to a "ThreadData-attached" shared TT; the per-SearchContextownership described here is what the code does.)
