use core::marker::PhantomData;
use crate::{
Collection,
Signature,
components::PushPopCollection,
storage::StorageBackend,
strategy::{Hooked, Strategy},
};
pub(crate) const DEFAULT_QUEUE_CAP: usize = 32;
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Hash)]
pub(crate) struct BanditCore<Q, S, B, C, const SUB_CAP: usize = DEFAULT_QUEUE_CAP> {
strategy: S,
sub_collections: B,
collection_state: C,
_p: PhantomData<Q>,
}
impl<Q, S, B, C, const SUB_CAP: usize> BanditCore<Q, S, B, C, SUB_CAP>
where
S: Default,
{
pub(crate) fn new_with(queues: B, states: C) -> Self {
Self {
strategy: S::default(),
sub_collections: queues,
collection_state: states,
_p: PhantomData,
}
}
}
impl<Q, S, B, C, const SUB_CAP: usize> BanditCore<Q, S, B, C, SUB_CAP>
where
B: StorageBackend<Q>,
{
pub(crate) fn arm_count(&self) -> usize {
self.sub_collections.len()
}
}
impl<Q, S, B, C, const SUB_CAP: usize> BanditCore<Q, S, B, C, SUB_CAP>
where
S: Strategy<Q>,
Q: Collection,
{
pub(crate) fn buy_in(&self) -> BanditHandle<'_, Q, S, B, C, SUB_CAP> {
BanditHandle {
parent: self,
gambler: self.strategy.create_gambler(),
}
}
}
impl<Q, S, B, C, const SUB_CAP: usize> BanditCore<Q, S, B, C, SUB_CAP>
where
B: StorageBackend<Q>,
{
pub(crate) fn into_arms(self) -> impl Iterator<Item = B::Item> {
self.sub_collections.into_iter()
}
}
impl<Q, S, B, C, const SUB_CAP: usize> BanditCore<Q, S, B, C, SUB_CAP>
where
Q: IntoIterator,
B: IntoIterator<Item = Q>,
{
pub(crate) fn into_items(self) -> impl Iterator<Item = Q::Item> {
self.sub_collections
.into_iter()
.flat_map(|collection| collection.into_iter())
}
}
#[must_use]
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct BanditHandle<
'a,
Q: Collection,
S: Strategy<Q>,
B,
C,
const SUB_CAP: usize = DEFAULT_QUEUE_CAP,
> {
parent: &'a BanditCore<Q, S, B, C, SUB_CAP>,
gambler: S::Gambler,
}
impl<'a, Q, S, B, C, const SUB_CAP: usize> BanditHandle<'a, Q, S, B, C, SUB_CAP>
where
Q: Collection,
S: Strategy<Q>,
B: StorageBackend<Q>,
C: StorageBackend<<S::Gambler as Hooked>::Stake>,
{
#[inline]
pub fn fork(&mut self) -> Self {
Self {
parent: self.parent,
gambler: S::fork_gambler(&self.parent.strategy, &mut self.gambler),
}
}
#[inline]
pub fn offer<'b, 'c>(
&'c mut self,
item: <Q::OfferSignature as Signature>::Input<'b>,
) -> Result<
<Q::OfferSignature as Signature>::Output<'b, 'c>,
<Q::OfferSignature as Signature>::Error<'b, 'c>,
> {
let i = self
.parent
.strategy
.choose_offer_arm(&self.parent.collection_state, &mut self.gambler);
match self.parent.sub_collections[i].offer(item) {
Ok(r) => {
self.gambler.on_offer_succ(&self.parent.collection_state[i]);
Ok(r)
}
Err(e) => {
self.gambler.on_offer_fail(&self.parent.collection_state[i]);
Err(e)
}
}
}
#[inline]
pub fn poll<'b, 'c>(
&'c mut self,
input: <Q::PollSignature as Signature>::Input<'b>,
) -> Result<
<Q::PollSignature as Signature>::Output<'b, 'c>,
<Q::PollSignature as Signature>::Error<'b, 'c>,
> {
Self::poll_internal(self.parent, &mut self.gambler, input).0
}
#[expect(clippy::type_complexity)]
#[inline]
pub fn poll_with_info<'b, 'c>(
&'c mut self,
input: <Q::PollSignature as Signature>::Input<'b>,
) -> (
Result<
<Q::PollSignature as Signature>::Output<'b, 'c>,
<Q::PollSignature as Signature>::Error<'b, 'c>,
>,
<S::Gambler as Hooked>::Stake,
)
where
<S::Gambler as Hooked>::Stake: Clone,
{
let (res, idx) = Self::poll_internal(self.parent, &mut self.gambler, input);
(res, self.parent.collection_state[idx].clone())
}
#[expect(clippy::type_complexity)]
pub(crate) fn poll_internal<'b, 'c>(
parent: &'c BanditCore<Q, S, B, C, SUB_CAP>,
gambler: &mut S::Gambler,
input: <Q::PollSignature as Signature>::Input<'b>,
) -> (
Result<
<Q::PollSignature as Signature>::Output<'b, 'c>,
<Q::PollSignature as Signature>::Error<'b, 'c>,
>,
usize,
) {
let i = parent
.strategy
.choose_poll_arm(&parent.collection_state, gambler);
match parent.sub_collections[i].poll(input) {
Ok(r) => {
gambler.on_poll_succ(&parent.collection_state[i]);
(Ok(r), i)
}
Err(e) => {
gambler.on_poll_fail(&parent.collection_state[i]);
let r = parent.strategy.collect(
&parent.collection_state,
&parent.sub_collections,
input,
);
if let Some((r, state)) = r {
gambler.on_poll_succ(&parent.collection_state[state]);
(Ok(r), state)
} else {
(Err(e), i)
}
}
}
}
#[inline]
pub fn state(&self) -> impl Iterator<Item = &<S::Gambler as Hooked>::Stake> {
self.parent.collection_state.iter()
}
#[inline]
pub fn len(&self) -> usize {
self.parent.sub_collections.iter().map(|q| q.len()).sum()
}
#[inline]
pub fn capacity(&self) -> usize {
self.parent
.sub_collections
.iter()
.map(|q| q.capacity())
.sum()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'a, Q, S, B, C, const SUB_CAP: usize> BanditHandle<'a, Q, S, B, C, SUB_CAP>
where
B: StorageBackend<Q>,
S: Strategy<Q>,
Q: Collection,
{
#[inline]
pub fn arm_count(&self) -> usize {
self.parent.arm_count()
}
}
impl<'a, Q, S, B, C, const SUB_CAP: usize> BanditHandle<'a, Q, S, B, C, SUB_CAP>
where
Q: PushPopCollection,
S: Strategy<Q>,
B: StorageBackend<Q>,
C: StorageBackend<<S::Gambler as Hooked>::Stake>,
{
#[inline]
pub fn push(&mut self, item: Q::Item) -> Result<(), Q::Item> {
self.offer(item)
}
#[inline]
pub fn pop(&mut self) -> Option<Q::Item> {
self.poll(()).ok()
}
}