This guide helps you migrate from the legacy C API to the modern C++ API.
DDS provides two API layers:
- Header:
#include <dds/dds.hpp> - Core Type:
SolverContext- instance-scoped solver state - Configuration:
SolverConfig- per-context settings - Memory Management: RAII - automatic cleanup
- Threading: Implicit - one context per thread
- Header:
#include <api/dll.h> - State: Global and thread-local
- Configuration:
SetThreading(),SetResources(), etc. - Memory Management: Manual -
FreeMemory()required - Threading: Explicit backend selection
| Feature | Modern API | Legacy API |
|---|---|---|
| Memory Safety | RAII automatic cleanup | Manual FreeMemory() calls |
| Thread Safety | One context per thread | Global state with locking |
| Configuration | Per-instance via SolverConfig |
Global via SetResources() |
| TT Lifecycle | Explicit control (reset_for_solve()) |
Implicit global pool |
| Performance | Equal or better | Baseline |
| Flexibility | Multiple contexts, different configs | Single global config |
- No resource leaks - Context automatically cleans up when destroyed
- Better multithreading - Each thread owns its context (no contention)
- Explicit lifecycle - You control when to reset or clear transposition tables
- Type safety - C++ types and compile-time checks
- Modern patterns - Smart pointers, RAII, standard library integration
| Legacy Function | Modern Replacement | Notes |
|---|---|---|
SetThreading(code) |
Create one SolverContext per thread |
Threading is implicit |
SetMaxThreads(n) |
One SolverContext per worker thread |
Control thread count in your app |
SetResources(mem, threads) |
SolverConfig::tt_mem_maximum_mb_ + per-thread contexts |
Split memory and thread control |
FreeMemory() |
Context destruction | Automatic via RAII |
The solving functions remain compatible but have new overloads:
// Legacy C API
int SolveBoard(struct Deal dl, int target, int solutions, int mode,
struct FutureTricks* futp, int thrId);
// Modern C++ API - new overload
int SolveBoard(SolverContext& ctx, const Deal& dl, int target,
int solutions, int mode, struct FutureTricks* futp);#include <api/dll.h>
int main() {
// Global configuration
SetMaxThreads(4);
SetResources(2000, 4);
// Solve a board
Deal dl;
// ... initialize dl ...
FutureTricks fut;
int res = SolveBoard(dl, -1, 3, 0, &fut, 0);
if (res == RETURN_NO_FAULT) {
// Use results
printf("Tricks: %d\n", fut.score[0]);
}
// Manual cleanup
FreeMemory();
return 0;
}#include <dds/dds.hpp>
#include <memory>
int main() {
// Per-instance configuration
SolverConfig cfg;
cfg.tt_kind_ = TTKind::Large;
cfg.tt_mem_default_mb_ = 2000;
cfg.tt_mem_maximum_mb_ = 2000;
// Create context
SolverContext ctx(cfg);
// Solve a board
Deal dl;
// ... initialize dl ...
FutureTricks fut;
int res = SolveBoard(ctx, dl, -1, 3, 0, &fut);
if (res == RETURN_NO_FAULT) {
// Use results
std::cout << "Tricks: " << fut.score[0] << "\n";
}
// Automatic cleanup when ctx goes out of scope
return 0;
}#include <api/dll.h>
void solve_many_boards(Deal* boards, int count) {
SetResources(2000, 4);
for (int i = 0; i < count; i++) {
FutureTricks fut;
SolveBoard(boards[i], -1, 3, 0, &fut, 0);
// Process results...
}
FreeMemory();
}#include <dds/dds.hpp>
void solve_many_boards(const std::vector<Deal>& boards) {
SolverConfig cfg;
cfg.tt_kind_ = TTKind::Large;
cfg.tt_mem_default_mb_ = 2000;
cfg.tt_mem_maximum_mb_ = 2000;
SolverContext ctx(cfg);
for (const auto& board : boards) {
FutureTricks fut;
SolveBoard(ctx, board, -1, 3, 0, &fut);
// Process results...
// TT accumulates knowledge across boards
}
// Optional: clear TT between unrelated problems
// ctx.reset_for_solve();
// Automatic cleanup
}#include <api/dll.h>
#include <pthread.h>
void* worker(void* arg) {
int thrId = *(int*)arg;
Deal dl;
// ... initialize dl ...
FutureTricks fut;
SolveBoard(dl, -1, 3, 0, &fut, thrId); // Thread ID required
return NULL;
}
int main() {
SetMaxThreads(4);
SetResources(2000, 4);
pthread_t threads[4];
int ids[4] = {0, 1, 2, 3};
for (int i = 0; i < 4; i++) {
pthread_create(&threads[i], NULL, worker, &ids[i]);
}
for (int i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
FreeMemory();
return 0;
}#include <dds/dds.hpp>
#include <thread>
#include <vector>
void worker(Deal dl) {
// Each thread creates its own context
SolverConfig cfg;
cfg.tt_kind_ = TTKind::Large;
cfg.tt_mem_default_mb_ = 500; // 500MB per thread (default TT size)
cfg.tt_mem_maximum_mb_ = 500; // Cap TT size at 500MB per thread
SolverContext ctx(cfg);
FutureTricks fut;
SolveBoard(ctx, dl, -1, 3, 0, &fut);
// Process results...
// Automatic cleanup when thread exits
}
int main() {
std::vector<Deal> deals;
// ... initialize deals ...
std::vector<std::thread> threads;
for (const auto& dl : deals) {
threads.emplace_back(worker, dl);
}
for (auto& t : threads) {
t.join();
}
return 0;
}class BatchSolver
{
public:
BatchSolver()
: ctx_(default_config())
{
}
auto solve(const Deal& dl) -> FutureTricks
{
FutureTricks fut;
SolveBoard(ctx_, dl, -1, 3, 0, &fut);
return fut;
}
auto reset() -> void
{
ctx_.reset_for_solve(); // Clear TT for new problem set
}
private:
SolverContext ctx_;
static auto default_config() -> SolverConfig
{
SolverConfig cfg;
cfg.tt_kind_ = TTKind::Large;
cfg.tt_mem_default_mb_ = 2000;
cfg.tt_mem_maximum_mb_ = 2000;
return cfg;
}
};void solve_tournament(const std::vector<Deal>& boards) {
SolverContext ctx;
for (size_t i = 0; i < boards.size(); i++) {
FutureTricks fut;
SolveBoard(ctx, boards[i], -1, 3, 0, &fut);
// Clear TT between rounds but not within rounds
if ((i + 1) % 32 == 0) {
ctx.reset_for_solve(); // New round, unrelated boards
}
}
}SolverConfig make_small_config() {
SolverConfig cfg;
cfg.tt_kind_ = TTKind::Small; // Use small TT implementation
cfg.tt_mem_default_mb_ = 100; // Default TT size 100MB
cfg.tt_mem_maximum_mb_ = 100; // Hard cap 100MB
return cfg;
}
void solve_on_embedded(const Deal& dl) {
SolverContext ctx(make_small_config());
FutureTricks fut;
SolveBoard(ctx, dl, -1, 3, 0, &fut);
}- Legacy API: Global pool shared across threads
- Modern API: Per-context allocation (multiply by thread count)
Recommendation: In multithreaded scenarios, reduce tt_mem_maximum_mb_ per context to avoid over-allocation.
The modern API gives you explicit control over TT lifecycle:
SolverContext ctx;
// Scenario 1: Related boards (same deal, different contracts)
// Keep TT - it accumulates useful positions
for (auto contract : contracts) {
solve_with_contract(ctx, deal, contract);
// NO reset - TT knowledge carries over
}
// Scenario 2: Unrelated boards
// Reset TT - old positions won't help
for (auto deal : unrelated_deals) {
ctx.reset_for_solve(); // Clear between deals
solve_board(ctx, deal);
}- Legacy API: Locking on shared global state
- Modern API: No locking (each thread owns its context)
Result: Better parallelism with modern API.
A: No. It remains supported indefinitely for binary compatibility. However, new features may only appear in the modern API.
A: Yes, but not recommended. Choose one approach for consistency. The modern API is preferred for new code.
A: No. Just update your #include directive and use the new API. Both APIs link against the same library.
A: The legacy C API remains fully supported. Consider wrapping the modern C++ API if you can link against C++.
A: The modern API is equal or slightly better due to reduced locking and better cache locality. Solve quality is identical.
A:
- Keep using legacy API with deprecation warnings
- Add
#include <dds/dds.hpp>alongside<api/dll.h> - Migrate one function/module at a time
- Remove legacy includes once complete
A: Report issues on GitHub. The legacy API will remain functional while you migrate.
- Legacy C API Reference - Full documentation of deprecated functions
- SolverContext Documentation - Modern API details
- Build System Guide - Bazel configuration
- Examples - Sample programs using both APIs
| If you need... | Use... |
|---|---|
| Simple single-threaded solving | Modern API - simplest, safest |
| Batch processing with TT reuse | Modern API - explicit control |
| Multi-threaded solving | Modern API - better parallelism |
| C-only compatibility | Legacy API - still supported |
| Existing code maintenance | Legacy API - no rush to migrate |
Recommendation for new projects: Use the modern C++ API with SolverContext. It's safer, clearer, and more flexible.