use std::num::NonZeroUsize;
use bon::Builder;
use kithara_assets::AssetStore;
use kithara_bufpool::HasPool;
use kithara_derive::Patch;
use kithara_platform::{CancelToken, tokio::runtime::Handle as RuntimeHandle};
use kithara_play::{CrossfadeSettings, PlayerImpl};
use crate::{ActionAtItemEnd, PlaybackOrder};
pub(crate) const DEFAULT_MAX_CONCURRENT_LOADS: NonZeroUsize = match NonZeroUsize::new(3) {
Some(n) => n,
None => unreachable!(),
};
pub(crate) const DEFAULT_PREFETCH_DURATION: f32 = 3.5;
#[derive(Builder, derive_more::Debug, Patch)]
#[builder(state_mod(vis = "pub"))]
#[non_exhaustive]
pub struct QueueConfig<S>
where
S: HasPool<u8> + HasPool<f32> + Send + Sync + 'static,
{
#[builder(default)]
pub action_at_item_end: ActionAtItemEnd,
#[builder(default)]
pub crossfade_settings: CrossfadeSettings,
#[builder(default = DEFAULT_MAX_CONCURRENT_LOADS)]
pub max_concurrent_loads: NonZeroUsize,
#[patch(skip)]
#[debug(skip)]
pub cancel: Option<CancelToken>,
#[patch(skip)]
#[debug(skip)]
pub store: Option<AssetStore<S>>,
#[builder(default)]
pub playback_order: PlaybackOrder,
#[patch(skip)]
#[debug(skip)]
pub runtime: Option<RuntimeHandle>,
#[patch(skip)]
#[debug(skip)]
pub player: PlayerImpl<S>,
#[builder(default = false)]
#[patch(skip)]
pub should_autoplay: bool,
#[builder(default = DEFAULT_PREFETCH_DURATION)]
pub prefetch_duration: f32,
#[builder(default = 100)]
pub max_history_size: usize,
}
#[cfg(test)]
mod tests {
use kithara_play::{PlayWorker, PlayWorkerConfig, PlayerConfig};
use kithara_test_utils::kithara;
use super::*;
use crate::{
queue::{TEST_SAMPLE_RATE, test_session},
test_pools::pools,
};
pub(super) fn config() -> QueueConfig<crate::test_pools::TestPools> {
let worker = PlayWorker::new(PlayWorkerConfig::builder(pools()).build());
let player = PlayerImpl::new(
PlayerConfig::builder()
.sample_rate(TEST_SAMPLE_RATE)
.worker(worker)
.session(test_session())
.build(),
);
QueueConfig::builder().player(player).build()
}
#[kithara::test]
fn default_config_has_reasonable_loader_cap() {
let cfg = config();
assert_eq!(cfg.max_concurrent_loads.get(), 3);
assert!(cfg.store.is_none());
assert!((cfg.prefetch_duration - 3.5).abs() < f32::EPSILON);
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod document_tests {
use kithara_test_utils::kithara;
use super::{QueueConfigPatch, tests::config};
#[kithara::test(native, flash(false))]
fn a_document_sets_the_load_cap_and_leaves_the_history_size() {
let patch: QueueConfigPatch =
serde_yaml_ng::from_str("max_concurrent_loads: 5\n").expect("the document types");
let mut config = config();
config.max_history_size = 37;
config.apply(patch);
assert_eq!(config.max_concurrent_loads.get(), 5);
assert_eq!(
config.max_history_size, 37,
"a key the document does not name must keep its seeded value"
);
}
#[kithara::test(native, flash(false))]
fn an_unknown_field_is_rejected_and_named() {
let error = serde_yaml_ng::from_str::<QueueConfigPatch>("concurrent_load_cap: 5\n")
.expect_err("a typo must not be silently ignored");
assert!(error.to_string().contains("concurrent_load_cap"), "{error}");
}
}