use std::collections::{BTreeSet, HashMap};
use crate::{
AddressingMode, Instruction, ProgramCounter, RegisterIndex,
RollingRecordIndex
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Plan(Vec<Vec<Step>>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Step
{
Split(Location),
Merge
{
split: usize,
survivor: Option<Location>,
dead: Vec<Location>
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Location
{
Register(RegisterIndex),
RollingRecord(RollingRecordIndex),
Answer
}
impl Location
{
pub(crate) fn read_by(op: AddressingMode) -> Option<Self>
{
match op
{
AddressingMode::Immediate(_) => None,
AddressingMode::Register(reg) => Some(Location::Register(reg)),
AddressingMode::RollingRecord(rec) =>
{
Some(Location::RollingRecord(rec))
},
}
}
pub(crate) fn written_by(inst: &Instruction) -> Self
{
match inst.destination()
{
Some(AddressingMode::Register(reg)) => Location::Register(reg),
Some(AddressingMode::RollingRecord(rec)) =>
{
Location::RollingRecord(rec)
},
Some(AddressingMode::Immediate(_)) => unreachable!(),
None => Location::Answer
}
}
}
impl Plan
{
pub(crate) fn new(instructions: &[Instruction]) -> Self
{
let values = Values::number(instructions);
Self(values.schedule(instructions.len()))
}
pub(crate) fn steps(&self, pc: ProgramCounter) -> &[Step] { &self.0[pc.0] }
}
#[derive(Debug)]
struct Value
{
location: Location,
random: bool,
readers: Vec<ProgramCounter>,
lasting: bool,
depends_on: BTreeSet<usize>
}
impl Value
{
fn fans_out(&self) -> bool { self.random && self.readers.len() > 1 }
fn is_live_after(&self, pc: ProgramCounter) -> bool
{
self.lasting || self.readers.last().is_some_and(|last| *last > pc)
}
}
#[derive(Debug)]
struct Values
{
values: Vec<Value>,
reads: Vec<Vec<usize>>,
writes: Vec<usize>
}
impl Values
{
fn number(instructions: &[Instruction]) -> Self
{
let mut values = Vec::<Value>::new();
let mut reads = Vec::with_capacity(instructions.len());
let mut writes = Vec::with_capacity(instructions.len());
let mut current = HashMap::<Location, usize>::new();
for (pc, inst) in instructions.iter().enumerate()
{
let pc = ProgramCounter(pc);
let mut read = Vec::<usize>::new();
for location in
inst.sources().into_iter().filter_map(Location::read_by)
{
if let Some(&value) = current.get(&location)
&& !read.contains(&value)
{
read.push(value);
values[value].readers.push(pc);
}
}
let rolls = matches!(
inst,
Instruction::RollRange(_)
| Instruction::RollStandardDice(_)
| Instruction::RollCustomDice(_)
);
let random = rolls || read.iter().any(|&v| values[v].random);
let location = Location::written_by(inst);
let value = values.len();
values.push(Value {
location,
random,
readers: Vec::new(),
lasting: false,
depends_on: BTreeSet::new()
});
current.insert(location, value);
reads.push(read);
writes.push(value);
}
if let Some(&answer) = current.get(&Location::Answer)
{
values[answer].lasting = true;
}
for (pc, &value) in writes.iter().enumerate()
{
let mut depends_on = reads[pc]
.iter()
.flat_map(|&v| values[v].depends_on.iter().copied())
.collect::<BTreeSet<_>>();
if values[value].fans_out()
{
depends_on.insert(value);
}
values[value].depends_on = depends_on;
}
Self {
values,
reads,
writes
}
}
}
impl Values
{
fn schedule(&self, len: usize) -> Vec<Vec<Step>>
{
let mut plan = Vec::with_capacity(len);
let mut live = BTreeSet::<usize>::new();
let mut current = BTreeSet::<usize>::new();
let mut splits = Vec::<usize>::new();
for pc in 0..len
{
let pc = ProgramCounter(pc);
let mut steps = Vec::new();
for &value in &self.reads[pc.0]
{
if !self.values[value].is_live_after(pc)
{
live.remove(&value);
}
}
let value = self.writes[pc.0];
let location = self.values[value].location;
current.retain(|&v| self.values[v].location != location);
current.insert(value);
if self.values[value].is_live_after(pc)
{
live.insert(value);
}
if self.values[value].fans_out()
{
splits.push(value);
steps.push(Step::Split(self.values[value].location));
}
'merge: loop
{
for split in (0..splits.len()).rev()
{
if let Some(survivor) = self.survivor(&splits, split, &live)
{
let dead = self.dead(splits[split], survivor, ¤t);
splits.remove(split);
steps.push(Step::Merge {
split,
survivor,
dead
});
continue 'merge
}
}
break
}
plan.push(steps);
}
debug_assert!(splits.is_empty(), "every split merges by the end");
plan
}
fn survivor(
&self,
splits: &[usize],
split: usize,
live: &BTreeSet<usize>
) -> Option<Option<Location>>
{
let value = splits[split];
if splits[split + 1..]
.iter()
.any(|&above| self.values[above].depends_on.contains(&value))
{
return None
}
let mut dependents = live
.iter()
.filter(|&&v| self.values[v].depends_on.contains(&value));
match (dependents.next(), dependents.next())
{
(None, _) => Some(None),
(Some(&survivor), None) if survivor != value =>
{
match self.values[survivor].location
{
Location::RollingRecord(_) => None,
location => Some(Some(location))
}
},
_ => None
}
}
fn dead(
&self,
split: usize,
survivor: Option<Location>,
current: &BTreeSet<usize>
) -> Vec<Location>
{
current
.iter()
.map(|&v| &self.values[v])
.filter(|v| {
v.depends_on.contains(&split) && Some(v.location) != survivor
})
.map(|v| v.location)
.collect()
}
}