#[cfg(feature = "parallel-histogram")]
pub mod parallel;
pub mod serial;
use std::{
collections::{HashMap, hash_map::IntoIter},
fmt::{Display, Formatter},
ops::{Deref, DerefMut, Index},
sync::{
OnceLock,
atomic::{AtomicU64, Ordering}
}
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
Add, AddressingMode, CanAllocate, CanVisitInstructions, Div, DropHighest,
DropLowest, EvaluationError, Evaluator, Exp, Function, Instruction,
InstructionVisitor, Max, Mod, Mul, Neg, ProgramCounter, RegisterIndex,
Return, RollCustomDice, RollRange, RollStandardDice, RollingRecord,
RollingRecordIndex, RollingRecordKind, Sub, SumRollingRecord, add, div,
exp, max, r#mod, mul, neg, sub
};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Histogram(pub HashMap<i32, u64>);
impl Histogram
{
pub fn get(&self, outcome: i32) -> u64
{
self.0.get(&outcome).copied().unwrap_or(0)
}
#[inline]
pub fn total(&self) -> u64 { self.0.values().sum() }
#[inline]
pub fn unique(&self) -> u64 { self.0.len() as u64 }
pub fn outcomes(&self) -> Vec<i32>
{
let mut outcomes: Vec<_> = self.0.keys().copied().collect();
outcomes.sort();
outcomes
}
pub fn odds(&self, outcome: i32) -> (u64, u64)
{
let count = self.get(outcome);
let total = self.total();
(count, total - count)
}
pub fn percent_chance(&self, outcome: i32) -> f64
{
(self.get(outcome) as f64 / self.total() as f64) * 100.0
}
}
impl Deref for Histogram
{
type Target = HashMap<i32, u64>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for Histogram
{
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl IntoIterator for Histogram
{
type Item = (i32, u64);
type IntoIter = IntoIter<i32, u64>;
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
}
impl Index<i32> for Histogram
{
type Output = u64;
fn index(&self, outcome: i32) -> &Self::Output
{
self.0.get(&outcome).unwrap_or(&0)
}
}
impl Display for Histogram
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result
{
let mut outcomes: Vec<_> = self.0.iter().collect();
outcomes.sort_by_key(|(outcome, _)| *outcome);
for (outcome, count) in outcomes
{
writeln!(f, "{}: {}", outcome, count)?;
}
Ok(())
}
}
pub trait HistogramBuilder<'inst, T>
where
T: 'inst
{
fn new(evaluator: Evaluator) -> Self;
fn build(
&self,
args: impl IntoIterator<Item = i32> + Send
) -> Result<Histogram, EvaluationError<'_>>
{
self.build_metered(args, u64::MAX)
}
fn build_metered(
&self,
args: impl IntoIterator<Item = i32> + Send,
budget: u64
) -> Result<Histogram, EvaluationError<'static>>;
fn build_with_limit(
&self,
args: impl IntoIterator<Item = i32> + Send,
limit: u64
) -> Result<Histogram, EvaluationError<'_>>
{
let count = AtomicU64::new(0);
self.build_while(args, |_| {
count.fetch_add(1, Ordering::Relaxed) < limit
})
}
fn build_while(
&self,
args: impl IntoIterator<Item = i32> + Send,
condition: impl Fn(&i32) -> bool + Send + Sync
) -> Result<Histogram, EvaluationError<'_>>;
fn iter(
&'inst self,
args: impl IntoIterator<Item = i32>
) -> Result<T, EvaluationError<'inst>>;
}
#[derive(Debug, Clone)]
pub struct EvaluationState<'inst>
{
instructions: &'inst [Instruction],
pc: ProgramCounter,
registers: Vec<i32>,
records: Vec<RollingRecord>,
substate: (i32, i64),
out_of_gas: bool,
successors: Option<Vec<EvaluationState<'inst>>>,
meter: Option<&'inst Meter>,
pub result: Option<i32>
}
#[derive(Debug)]
pub(crate) struct Meter
{
budget: u64,
remaining: AtomicU64,
refusal: OnceLock<(u64, u64, u64)>
}
impl Meter
{
pub(crate) fn new(budget: u64) -> Self
{
Meter {
budget,
remaining: AtomicU64::new(budget),
refusal: OnceLock::new()
}
}
fn charge(&self, branches: u64) -> bool
{
let mut remaining = self.remaining.load(Ordering::Relaxed);
loop
{
if branches > remaining
{
let consumed = self.budget - remaining;
let _ = self.refusal.set((branches, remaining, consumed));
return false
}
match self.remaining.compare_exchange_weak(
remaining,
remaining - branches,
Ordering::Relaxed,
Ordering::Relaxed
)
{
Ok(_) => return true,
Err(actual) => remaining = actual
}
}
}
#[inline]
pub(crate) fn limited(&self) -> Option<&Self>
{
(self.budget < u64::MAX).then_some(self)
}
#[inline]
pub(crate) fn refused(&self) -> bool { self.refusal.get().is_some() }
pub(crate) fn verdict(&self) -> Result<(), EvaluationError<'static>>
{
match self.refusal.get()
{
None => Ok(()),
Some(&(requested, remaining, consumed)) =>
{
Err(EvaluationError::HistogramBudgetExhausted {
requested,
remaining,
consumed
})
},
}
}
}
trait CanBuildHistogram<'inst, T>
where
T: 'inst
{
fn function(&self) -> &Function;
fn environment(&self) -> &HashMap<usize, i32>;
fn create_iterator(
&'inst self,
initial_state: EvaluationState<'inst>,
meter: Option<&'inst Meter>
) -> T;
fn iter(
&'inst self,
args: impl IntoIterator<Item = i32>,
meter: Option<&'inst Meter>
) -> Result<T, EvaluationError<'static>>
{
let function = self.function();
let arity = function.arity();
let args = args.into_iter().collect::<Vec<_>>();
if args.len() != arity
{
return Err(EvaluationError::BadArity {
expected: arity,
given: args.len()
});
}
let mut state = EvaluationState::initial(function, meter);
for (i, arg) in args.into_iter().enumerate()
{
state.registers[i] = arg;
}
for (index, value) in self.environment()
{
state.registers[arity + *index] = *value;
}
Ok(self.create_iterator(state, meter))
}
}
trait CanIterate<'inst>: Sized
{
fn next_state(&mut self) -> Option<EvaluationState<'inst>>;
fn inject_successors(&mut self, state: &mut EvaluationState<'inst>);
}
impl<'inst> EvaluationState<'inst>
{
fn initial(function: &'inst Function, meter: Option<&'inst Meter>) -> Self
{
EvaluationState {
instructions: &function.instructions,
pc: ProgramCounter::default(),
registers: vec![0; function.register_count],
records: vec![
RollingRecord::default();
function.rolling_record_count
],
substate: (0, 0),
out_of_gas: false,
successors: None,
meter,
result: None
}
}
pub(crate) const MAX_SUCCESSORS: usize = 30;
fn evaluate(&mut self) -> Option<i32>
{
for inst in &self.instructions[self.pc.0..]
{
inst.visit(self).unwrap();
self.pc.allocate();
self.substate = (0, 0);
if self.out_of_gas
{
return None
}
}
self.result
}
fn charge(&mut self, branches: u64) -> bool
{
let charged = self.meter.is_none_or(|meter| meter.charge(branches));
if !charged
{
self.out_of_gas = true;
}
charged
}
fn value(&self, op: AddressingMode) -> i32
{
match op
{
AddressingMode::Immediate(value) => value.0,
AddressingMode::Register(reg) => self.registers[reg.0],
AddressingMode::RollingRecord(_) => unreachable!()
}
}
#[inline]
fn set_register(&mut self, reg: RegisterIndex, value: i32)
{
self.registers[reg.0] = value;
}
#[inline]
fn record_mut(&mut self, op: RollingRecordIndex) -> &mut RollingRecord
{
&mut self.records[op.0]
}
fn add_successor(&mut self, successor: EvaluationState<'inst>) -> bool
{
match &mut self.successors
{
Some(successors) =>
{
successors.push(successor);
},
_ =>
{
self.successors = Some(vec![successor]);
}
}
self.out_of_gas =
self.successors.as_ref().unwrap().len() >= Self::MAX_SUCCESSORS - 1;
!self.out_of_gas
}
fn new_successor(&self) -> Self
{
let mut state = self.clone();
state.successors = None;
state.out_of_gas = false;
state
}
fn new_iteration_successor(
&self,
record: RollingRecordIndex,
iteration: i32,
max_iterations: i32,
result: i32
) -> Self
{
let mut successor = self.new_successor();
if iteration == max_iterations
{
successor.pc.allocate();
successor.substate = (0, 0);
}
else
{
successor.substate = (iteration, 0);
}
let record = successor.record_mut(record);
record.results.push(result);
successor
}
fn new_index_successor(&self, iteration: i32, index: i64) -> Self
{
let mut successor = self.new_successor();
successor.substate = (iteration, index);
successor
}
}
impl Display for EvaluationState<'_>
{
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result
{
write!(f, "{}{:?}", self.pc, self.substate)?;
if !self.records.is_empty()
{
write!(f, " {{ ")?;
for (i, record) in self.records.iter().enumerate()
{
if i > 0
{
write!(f, "; ")?;
}
write!(f, "{}", record)?;
}
write!(f, " }}")?;
}
if let Some(result) = self.result
{
write!(f, " -> {}", result)?;
}
Ok(())
}
}
impl InstructionVisitor<()> for EvaluationState<'_>
{
fn visit_roll_range(&mut self, inst: &RollRange) -> Result<(), ()>
{
let start = self.value(inst.start);
let end = self.value(inst.end);
{
let record = self.record_mut(inst.dest);
record.kind = RollingRecordKind::Range { start, end };
}
if end < start
{
let record = self.record_mut(inst.dest);
record.results.push(0);
return Ok(())
}
let (iteration, index) = self.substate;
if iteration == 0
{
let width = end as i64 - start as i64 + 1;
if index == 0 && !self.charge(width as u64)
{
return Ok(())
}
for index in index..width
{
if !self.add_successor(self.new_iteration_successor(
inst.dest,
iteration + 1,
1,
(start as i64 + index) as i32
))
{
self.add_successor(
self.new_index_successor(iteration, index + 1)
);
return Ok(())
}
}
self.out_of_gas = true;
return Ok(())
}
unreachable!()
}
fn visit_roll_standard_dice(
&mut self,
inst: &RollStandardDice
) -> Result<(), ()>
{
let count = self.value(inst.count);
let faces = self.value(inst.faces);
{
let record = self.record_mut(inst.dest);
record.kind = RollingRecordKind::Standard { count, faces };
}
if count <= 0
{
let record = self.record_mut(inst.dest);
record.results.push(0);
return Ok(())
}
let (iteration, index) = self.substate;
if iteration < count
{
if index == 0 && !self.charge(faces.max(1) as u64)
{
return Ok(())
}
if faces <= 0
{
self.add_successor(self.new_iteration_successor(
inst.dest,
iteration + 1,
count,
0
));
}
for index in index..faces as i64
{
if !self.add_successor(self.new_iteration_successor(
inst.dest,
iteration + 1,
count,
index as i32 + 1
))
{
self.add_successor(
self.new_index_successor(iteration, index + 1)
);
return Ok(())
}
}
self.out_of_gas = true;
return Ok(())
}
unreachable!()
}
fn visit_roll_custom_dice(
&mut self,
inst: &RollCustomDice
) -> Result<(), ()>
{
let count = self.value(inst.count);
let faces = inst.faces.len();
{
let record = self.record_mut(inst.dest);
record.kind = RollingRecordKind::Custom {
count,
faces: inst.faces.clone()
};
}
if count <= 0
{
let record = self.record_mut(inst.dest);
record.results.push(0);
return Ok(())
}
let (iteration, index) = self.substate;
if iteration < count
{
if index == 0 && !self.charge(faces.max(1) as u64)
{
return Ok(())
}
if faces == 0
{
self.add_successor(self.new_iteration_successor(
inst.dest,
iteration + 1,
count,
0
));
}
for index in index..faces as i64
{
if !self.add_successor(self.new_iteration_successor(
inst.dest,
iteration + 1,
count,
inst.faces[index as usize]
))
{
self.add_successor(
self.new_index_successor(iteration, index + 1)
);
return Ok(())
}
}
self.out_of_gas = true;
return Ok(())
}
unreachable!()
}
fn visit_drop_lowest(&mut self, inst: &DropLowest) -> Result<(), ()>
{
let count = self.value(inst.count);
let record = self.record_mut(inst.dest);
record.drop_lowest(count);
Ok(())
}
fn visit_drop_highest(&mut self, inst: &DropHighest) -> Result<(), ()>
{
let count = self.value(inst.count);
let record = self.record_mut(inst.dest);
record.drop_highest(count);
Ok(())
}
fn visit_sum_rolling_record(
&mut self,
inst: &SumRollingRecord
) -> Result<(), ()>
{
let sum = self.record_mut(inst.src).sum();
self.set_register(inst.dest, sum);
Ok(())
}
fn visit_add(&mut self, inst: &Add) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, add(op1, op2));
Ok(())
}
fn visit_sub(&mut self, inst: &Sub) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, sub(op1, op2));
Ok(())
}
fn visit_mul(&mut self, inst: &Mul) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, mul(op1, op2));
Ok(())
}
fn visit_div(&mut self, inst: &Div) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, div(op1, op2));
Ok(())
}
fn visit_mod(&mut self, inst: &Mod) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, r#mod(op1, op2));
Ok(())
}
fn visit_exp(&mut self, inst: &Exp) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, exp(op1, op2));
Ok(())
}
fn visit_max(&mut self, inst: &Max) -> Result<(), ()>
{
let op1 = self.value(inst.op1);
let op2 = self.value(inst.op2);
self.set_register(inst.dest, max(op1, op2));
Ok(())
}
fn visit_neg(&mut self, inst: &Neg) -> Result<(), ()>
{
let op = self.value(inst.op);
self.set_register(inst.dest, neg(op));
Ok(())
}
fn visit_return(&mut self, inst: &Return) -> Result<(), ()>
{
self.result = Some(self.value(inst.src));
Ok(())
}
}