Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions jni/exported_symbols.lds
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ _CalcAllTables
_CalcAllTablesN
_CalcAllTablesPBN
_CalcAllTablesPBNN
_CalcAllTablesPBNX
_CalcAllTablesX
_CalcDDtable
_CalcDDtableN
_CalcDDtablePBN
Expand Down
2 changes: 2 additions & 0 deletions jni/version_script.lds
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
CalcAllTablesN;
CalcAllTablesPBN;
CalcAllTablesPBNN;
CalcAllTablesPBNX;
CalcAllTablesX;
CalcDDtable;
CalcDDtableN;
CalcDDtablePBN;
Expand Down
37 changes: 37 additions & 0 deletions library/src/api/dll.h
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,43 @@ EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBNN(
struct AllParResults * presp,
int maxThreads) -> int;

/**
* @brief Unbounded CalcAllTables: any number of deals, one parallel board job.
*
* Legacy CalcAllTablesN remains capped at MAXNOOFTABLES. This entry point
* expands all deal×strain boards and solves them in a single
Comment thread
tameware marked this conversation as resolved.
* parallel_all_boards_n dispatch (heap-backed), matching the ddss large-batch
* shape while preserving the fixed-size ABI of the legacy structs.
*
* @param numDeals Number of deals (may exceed MAXNOOFTABLES)
* @param deals Flat array of numDeals deals
* @param mode Par mode (-1 = no par); par requires all strains and non-null par
* @param trumpFilter Per-strain filter (0 = include)
* @param results Output array of numDeals tables
* @param par Optional par output (numDeals); required when mode requests par
* @param maxThreads Worker cap; <= 0 means auto
*/
EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesX(
int numDeals,
struct DdTableDeal const * deals,
int mode,
int const trumpFilter[DDS_STRAINS],
struct DdTableResults * results,
struct ParResults * par,
int maxThreads) -> int;

/**
* @brief PBN variant of CalcAllTablesX.
*/
EXTERN_C DLLEXPORT auto STDCALL CalcAllTablesPBNX(
int numDeals,
struct DdTableDealPBN const * deals,
int mode,
int const trumpFilter[DDS_STRAINS],
struct DdTableResults * results,
struct ParResults * par,
int maxThreads) -> int;

/**
* @brief Solve multiple bridge deals in PBN format.
*
Expand Down
209 changes: 205 additions & 4 deletions library/src/calc_tables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "calc_tables.hpp"
#include <algorithm>
#include <array>
#include <numeric>
#include <vector>

Expand Down Expand Up @@ -249,7 +250,7 @@ int STDCALL CalcDDtable(
int STDCALL CalcAllTablesN(
DdTableDeals const * dealsp,
int mode,
int const trumpFilter[5],
int const trumpFilter[DDS_STRAINS],
DdTablesRes * resp,
AllParResults * presp,
int maxThreads)
Expand Down Expand Up @@ -355,7 +356,7 @@ int STDCALL CalcAllTablesN(
int STDCALL CalcAllTables(
DdTableDeals const * dealsp,
int mode,
int const trumpFilter[5],
int const trumpFilter[DDS_STRAINS],
DdTablesRes * resp,
AllParResults * presp)
{
Expand All @@ -366,7 +367,7 @@ int STDCALL CalcAllTables(
int STDCALL CalcAllTablesPBNN(
DdTableDealsPBN const * dealsp,
int mode,
int const trumpFilter[5],
int const trumpFilter[DDS_STRAINS],
DdTablesRes * resp,
AllParResults * presp,
int maxThreads)
Expand All @@ -386,14 +387,214 @@ int STDCALL CalcAllTablesPBNN(
int STDCALL CalcAllTablesPBN(
DdTableDealsPBN const * dealsp,
int mode,
int const trumpFilter[5],
int const trumpFilter[DDS_STRAINS],
DdTablesRes * resp,
AllParResults * presp)
{
return CalcAllTablesPBNN(dealsp, mode, trumpFilter, resp, presp, 0);
}


namespace
{

// Solve one strain board (all four declarers) into scores[DDS_HANDS].
auto calc_single_deal_scores(
SolverContext& ctx,
Deal deal,
const int target,
const int solutions,
const int mode,
int scores[DDS_HANDS]) -> int
{
FutureTricks fut{};
deal.first = 0;
int res = solve_board(ctx, deal, target, solutions, mode, &fut);
if (res != RETURN_NO_FAULT)
return res;
scores[0] = fut.score[0];

for (int k = 1; k < DDS_HANDS; k++)
{
const int hint = (k == 2 ? fut.score[0] : 13 - fut.score[0]);
deal.first = k;
res = solve_same_board(ctx, deal, &fut, hint);
if (res != RETURN_NO_FAULT)
return res;
scores[k] = fut.score[0];
}
return RETURN_NO_FAULT;
}

} // namespace


int STDCALL CalcAllTablesX(
int numDeals,
DdTableDeal const * deals,
int mode,
int const trumpFilter[DDS_STRAINS],
DdTableResults * results,
ParResults * par,
int maxThreads)
{
// C ABI: exceptions must not unwind into a foreign caller (UB). Heap
// allocations below (and parallel_all_boards_n) may throw; map any throw
// to RETURN_UNKNOWN_FAULT.
try
{
if (numDeals < 0)
return RETURN_TOO_MANY_TABLES;
if (numDeals == 0)
return RETURN_NO_FAULT;
if (deals == nullptr || results == nullptr || trumpFilter == nullptr)
return RETURN_UNKNOWN_FAULT;

int included = 0;
for (int k = 0; k < DDS_STRAINS; k++)
{
if (!trumpFilter[k])
included++;
}
if (included == 0)
return RETURN_NO_SUIT;

const bool want_par = (mode > -1) && (mode < 4) && (included == DDS_STRAINS);
if (want_par && par == nullptr)
return RETURN_UNKNOWN_FAULT;

// Expand every deal×included-strain into one board list and solve in a
// single parallel_all_boards_n job (heap-backed). This is the ddss-style
// large-batch shape; legacy CalcAllTablesN remains capped at MAXNOOFTABLES.
const int nboards = numDeals * included;
std::vector<Deal> boards(static_cast<unsigned>(nboards));
std::vector<std::array<int, DDS_HANDS>> scores(static_cast<unsigned>(nboards));

int ind = 0;
for (int m = 0; m < numDeals; m++)
{
for (int tr = DDS_STRAINS - 1; tr >= 0; tr--)
{
if (trumpFilter[tr])
continue;

Deal& dl = boards[static_cast<unsigned>(ind)];
for (int h = 0; h < DDS_HANDS; h++)
for (int s = 0; s < DDS_SUITS; s++)
dl.remainCards[h][s] = deals[m].cards[h][s];
dl.trump = tr;
dl.first = 0;
for (int k = 0; k <= 2; k++)
{
dl.currentTrickRank[k] = 0;
dl.currentTrickSuit[k] = 0;
}
ind++;
}
}

const int nthreads = resolve_worker_count(maxThreads, nboards);

// Hardest-first dispatch only helps with multiple workers (same idea as
// calc_all_boards_n). Skip fanout+sort on the single-thread path so large
// single-worker batches avoid O(n log n) overhead.
std::vector<int> order;
if (nthreads > 1)
{
order.resize(static_cast<unsigned>(nboards));
std::iota(order.begin(), order.end(), 0);
std::vector<int> fanout(static_cast<unsigned>(nboards));
for (int i = 0; i < nboards; i++)
fanout[static_cast<unsigned>(i)] =
dds::internal::deal_fanout(boards[static_cast<unsigned>(i)]);
std::stable_sort(order.begin(), order.end(),
[&](const int a, const int b) {
return fanout[static_cast<unsigned>(a)] > fanout[static_cast<unsigned>(b)];
});
}

const int err = parallel_all_boards_n(nboards, nthreads,
[&](const int worker_id, const int bno) -> int {
(void)worker_id;
return calc_single_deal_scores(
dds::internal::worker_solver_context(),
boards[static_cast<unsigned>(bno)],
-1, 1, 1,
scores[static_cast<unsigned>(bno)].data());
},
order.empty() ? nullptr : &order);
if (err != RETURN_NO_FAULT)
return err;

for (int m = 0; m < numDeals; m++)
{
for (int strainIndex = 0; strainIndex < included; strainIndex++)
{
const int index = m * included + strainIndex;
const int strain = boards[static_cast<unsigned>(index)].trump;
for (int first = 0; first < DDS_HANDS; first++)
{
results[m].res_table[strain][rho[first]] =
13 - scores[static_cast<unsigned>(index)][static_cast<unsigned>(first)];
}
}
}

if (want_par)
{
for (int k = 0; k < numDeals; k++)
{
const int res = Par(&results[k], &par[k], mode);
if (res != RETURN_NO_FAULT)
return res;
}
}

return RETURN_NO_FAULT;
}
catch (...)
{
return RETURN_UNKNOWN_FAULT;
}
}


int STDCALL CalcAllTablesPBNX(
int numDeals,
DdTableDealPBN const * deals,
int mode,
int const trumpFilter[DDS_STRAINS],
DdTableResults * results,
ParResults * par,
int maxThreads)
{
// C ABI: same catch-all contract as CalcAllTablesX / dds_c_api.cpp.
try
{
if (numDeals < 0)
return RETURN_TOO_MANY_TABLES;
if (numDeals == 0)
return RETURN_NO_FAULT;
if (deals == nullptr || results == nullptr || trumpFilter == nullptr)
return RETURN_UNKNOWN_FAULT;

std::vector<DdTableDeal> binary(static_cast<unsigned>(numDeals));
for (int i = 0; i < numDeals; ++i)
{
if (convert_from_pbn(deals[i].cards, binary[static_cast<unsigned>(i)].cards) != 1)
return RETURN_PBN_FAULT;
}

return CalcAllTablesX(
numDeals, binary.data(), mode, trumpFilter, results, par, maxThreads);
}
catch (...)
{
return RETURN_UNKNOWN_FAULT;
}
}


int STDCALL CalcDDtablePBNN(
DdTableDealPBN tableDealPBN,
DdTableResults * tablep,
Expand Down
14 changes: 14 additions & 0 deletions library/src/system/deal_fanout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,29 @@

#include "deal_fanout.hpp"

#include <atomic>

#include <lookup_tables/lookup_tables.hpp>

namespace dds
{
namespace internal
{

namespace
{
std::atomic<int> g_deal_fanout_call_count{0};
} // namespace

auto deal_fanout_call_count() -> int
{
return g_deal_fanout_call_count.load(std::memory_order_relaxed);
}

auto deal_fanout(const Deal& dl) -> int
{
g_deal_fanout_call_count.fetch_add(1, std::memory_order_relaxed);

// The fanout for a given suit and a given player is the number
// of bit groups, so KT982 has 3 groups. In a given suit the
// maximum number over all four players is 13.
Expand Down
3 changes: 3 additions & 0 deletions library/src/system/deal_fanout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ namespace internal
*/
auto deal_fanout(const Deal& dl) -> int;

/** Test seam: how many times deal_fanout() has been called process-wide. */
auto deal_fanout_call_count() -> int;

} // namespace internal
} // namespace dds
9 changes: 9 additions & 0 deletions library/src/system/parallel_boards.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace
{

std::atomic<std::uint64_t> g_threads_created{0};
std::atomic<int> g_last_job_board_count{0};


class BoardWorkerPool
Expand Down Expand Up @@ -274,6 +275,10 @@ auto parallel_boards_worker_threads_created() -> std::uint64_t
return g_threads_created.load(std::memory_order_relaxed);
}

auto parallel_boards_last_job_board_count() -> int
{
return g_last_job_board_count.load(std::memory_order_relaxed);
}

void shutdown_parallel_boards_pool()
{
Expand All @@ -297,6 +302,10 @@ auto parallel_all_boards_n(
const std::function<int(int worker_id, int bno)>& process_board,
const std::vector<int>* order) -> int
{
// Always record the requested count, including early-return paths, so the
// test seam cannot report a stale prior job.
g_last_job_board_count.store(count, std::memory_order_relaxed);

if (count <= 0)
{
return RETURN_NO_FAULT;
Expand Down
5 changes: 5 additions & 0 deletions library/src/system/parallel_boards.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ namespace dds::internal
// Cumulative number of OS worker threads created by the board pool (test seam).
auto parallel_boards_worker_threads_created() -> std::uint64_t;

// Board count of the most recent parallel_all_boards_n call (test seam),
// including calls that return early for count <= 0. Used to assert unbounded
// calc submits one job covering the full batch.
auto parallel_boards_last_job_board_count() -> int;

// Join and destroy the process-local board worker pool. Safe to call with no
// pool, and again after a later parallel_all_boards_n recreates it.
void shutdown_parallel_boards_pool();
Expand Down
Loading
Loading