use crate::ids::TeamId;
use indexmap::IndexMap;
use rand::RngCore;
use rand_pcg::Lcg64Xsh32;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use twgame_core::twsnap::time::{Duration, Instant, SnapTick};
type PrngCompat = HashMap<Instant, HashMap<TeamId, Vec<u32>>>;
#[derive(Debug)]
struct PrngInner {
prng: Lcg64Xsh32,
uses: Option<(Instant, HashMap<TeamId, Vec<u32>>, PrngCompat)>,
external_info: Option<HashMap<Instant, HashMap<TeamId, Vec<u32>>>>,
}
impl PrngInner {
fn random_bits(&mut self, team_id: TeamId, now: Instant, num: u32) -> u32 {
let mut rand = self.prng.next_u32();
if let Some(compat_data) = self.external_info.as_ref() {
if let Some(cur_tick) = compat_data.get(&now) {
rand = cur_tick[&team_id][num as usize];
}
}
if let Some((last_tick, last_compat, stored_compat_data)) = self.uses.as_mut() {
if *last_tick != now {
if last_compat.len() > 1 {
stored_compat_data.insert(*last_tick, last_compat.clone());
}
last_compat.clear();
}
*last_tick = now;
let team_values = last_compat.entry(team_id).or_default();
assert_eq!(num as usize, team_values.len());
team_values.push(rand);
}
rand
}
fn retrieve_compat_data(&mut self) -> Option<String> {
if let Some((last_tick, last_compat, mut stored_compat_data)) = self.uses.take() {
if last_compat.len() > 1 {
stored_compat_data.insert(last_tick, last_compat);
}
if stored_compat_data.is_empty() {
return Some(String::new());
}
let mut compat_data: IndexMap<SnapTick, IndexMap<i32, Vec<u32>>> = IndexMap::new();
let mut ticks: Vec<_> = stored_compat_data.keys().cloned().collect();
ticks.sort();
for tick in ticks {
compat_data.insert(tick.snap_tick(), IndexMap::new());
let mut team_ids: Vec<_> = stored_compat_data[&tick].keys().cloned().collect();
team_ids.sort();
for team_id in team_ids {
compat_data[&tick.snap_tick()].insert(
team_id.to_i32(),
stored_compat_data[&tick][&team_id].clone(),
);
}
}
Some(serde_json::to_string(&compat_data).unwrap())
} else {
None
}
}
}
#[derive(Debug)]
pub struct Prng {
inner: Rc<RefCell<PrngInner>>,
team_id: TeamId,
last_used: Instant,
num_used: u32,
}
impl Prng {
pub(super) fn new(team_id: TeamId, prng: Lcg64Xsh32) -> Self {
Self {
inner: Rc::new(RefCell::new(PrngInner {
prng,
uses: None,
external_info: None,
})),
team_id,
last_used: Default::default(),
num_used: 0,
}
}
pub(super) fn create_new(&self, team_id: TeamId) -> Self {
Self {
inner: self.inner.clone(),
team_id,
last_used: Instant::zero(),
num_used: 0,
}
}
pub(super) fn overwrite(&self, prng_description: Lcg64Xsh32) {
self.inner.borrow_mut().prng = prng_description;
}
pub(super) fn supply_compat_data(&self, compat_data: &str) {
if compat_data.is_empty() {
self.inner.borrow_mut().external_info = Some(HashMap::new());
return;
}
let compat_data: HashMap<SnapTick, HashMap<i32, Vec<u32>>> =
serde_json::from_str(compat_data).unwrap();
let compat_data: PrngCompat = compat_data
.into_iter()
.map(|(tick, teams)| {
(
Instant::zero() + Duration::from_ticks(tick),
teams
.into_iter()
.map(|(team_id, values)| (TeamId::from_i32(team_id), values))
.collect(),
)
})
.collect();
self.inner.borrow_mut().external_info = Some(compat_data);
}
pub(super) fn enable_compat_data_collection(&self) {
self.inner.borrow_mut().uses = Some((Instant::zero(), HashMap::new(), HashMap::new()));
}
pub(super) fn retrieve_compat_data(&self) -> Option<String> {
self.inner.borrow_mut().retrieve_compat_data()
}
}
impl Prng {
pub fn random_bits(&mut self, now: Instant) -> u32 {
if self.last_used == now {
self.num_used += 1;
} else {
self.last_used = now;
self.num_used = 0;
}
self.inner
.borrow_mut()
.random_bits(self.team_id, now, self.num_used)
}
pub fn random_or_0(&mut self, now: Instant, below_this: u32) -> u32 {
if below_this <= 1 {
0
} else {
self.random_bits(now) % below_this
}
}
}