@idealic/poker-engine
Version:
Professional poker game engine and hand evaluator with built-in iterator utilities
138 lines • 5.44 kB
JavaScript
import { pubsub } from './lib/pubsub';
/**
* Simple concating of nested iterables when no true concurrency is needed
*
* @param iterators An iterable of AsyncGenerators to process sequentially
*/
async function* _concat(iterators) {
for await (const iterator of iterators) {
for await (const item of iterator) {
yield item;
}
}
}
/**
* Processes multiple async iterables concurrently,
* yielding values as they become available from any iterator.
* Limits the number of concurrent iterators being processed.
*
* @param inputConcurrency Maximum number of iterators to process concurrently
* @param outputConcurrency Maximum number of values that can be buffered before consumption
* @param iterators An iterable of AsyncGenerators to process
*/
async function* _concatConcurrently(inputConcurrency = Infinity, outputConcurrency = inputConcurrency, iterators, isLazy = false) {
// Use pubsub to control output values
// % console.time('race');
const { publish, consume, producing } = pubsub(outputConcurrency);
let inputsExhausted = false;
// track how many items have been published but not consumed for each iterator
const counters = new Map();
const active = new Set();
/** Track how many items have been published but not consumed for an iterator */
function countBufferedValues(iterator, diff) {
const updated = (counters.get(iterator) || 0) + diff;
if (updated == 0) {
counters.delete(iterator);
}
else {
counters.set(iterator, updated);
}
return updated;
}
/** How many iterators are currently not fully consmed? */
function countActiveIterators() {
let count = counters.size;
for (const iterator of active) {
if (!counters.has(iterator)) {
count += 1;
}
}
return count;
}
let onNextIterator;
// Process a single iterator until it's exhausted
async function readIterator(iterator) {
const id = `race-iterator-${active.size + 1}`;
// %console.time(id);
// %console.timeLog(id, 'Processing iterator', iterator);
try {
active.add(iterator);
// % console.log('iterate this thing');
for await (const item of iterator) {
countBufferedValues(iterator, +1);
// % console.timeLog(id, 'Publishing value:', item);
await publish({ item, iterator });
// % console.timeLog(id, 'Published value:', item);
}
}
finally {
active.delete(iterator);
// % console.timeLog(id, 'Iterator exhausted');
nextIteratorIfNeeded(iterator);
}
}
function nextIteratorIfNeeded(iterator) {
if (onNextIterator &&
!active.has(iterator) &&
countBufferedValues(iterator, 0) == 0 &&
producing.size < outputConcurrency) {
//console.log('iterate this thing!', counters.size, active.size, producing.size);
// % console.log('iterate this thing', producing.size, outputConcurrency);
onNextIterator();
}
}
// Consume iterators up to concurrency limit
(async () => {
try {
// Collect and process iterators
let i = 0;
for await (const iterator of iterators) {
i++;
const id = `race-iterator-${i}`;
// % console.time(id);
// % console.timeLog(id, 'New iterator available', countActiveIterators(), iterator);
if (countActiveIterators() >= inputConcurrency) {
// % console.timeLog(id, 'WAITING FOR NEXT ITERATOR');
await new Promise(resolve => {
onNextIterator = resolve;
});
// % console.timeLog(id, 'Unblocked FOR NEXT ITERATOR');
}
// % console.timeLog(id, ' ITERATORS', counters.size);
readIterator(iterator);
}
// % console.timeLog('race', 'All input iterators exhausted', i);
}
finally {
inputsExhausted = true;
}
})();
// exit quickly if there are not inputs
await new Promise(setImmediate);
// % console.log('loop', inputsExhausted, counters.size, producing.size);
// Give a chance for state to update
// Yield values until all iterators are done
while (!(inputsExhausted && active.size === 0 && counters.size === 0 && producing.size === 0)) {
// %console.timeLog(
// % 'race',
// % `active iterators: ${counters.size}, inputs exhausted: ${inputsExhausted}`
// %);
// %
const { item, iterator } = await consume();
countBufferedValues(iterator, -1);
nextIteratorIfNeeded(iterator);
yield item;
}
}
export function concat(inputs, inputConcurrency, outputConcurrency) {
if (typeof inputs === 'number') {
return (iterators) => concat(iterators, inputs, inputConcurrency);
}
else if ((inputConcurrency === 1 && outputConcurrency === 1) || inputConcurrency == undefined) {
return _concat(inputs);
}
else {
return _concatConcurrently(inputConcurrency, outputConcurrency || Infinity, inputs);
}
}
//# sourceMappingURL=concat.js.map