use crate::CommitRecord;
use sley_core::ObjectId;
use std::collections::{HashMap, HashSet};
pub struct Bisection {
pub picks: Vec<usize>,
pub reaches: usize,
pub weights: Vec<i64>,
}
fn approx_halfway(weight: i64, nr: usize) -> bool {
let diff = 2 * weight - nr as i64;
match diff {
-1..=1 => true,
_ => diff.unsigned_abs() < (nr / 1024) as u64,
}
}
fn count_distance(start: usize, parents: &[Vec<usize>]) -> i64 {
let mut visited = vec![false; parents.len()];
let mut stack = vec![start];
let mut nr = 0i64;
while let Some(idx) = stack.pop() {
if visited[idx] {
continue;
}
visited[idx] = true;
nr += 1;
for &parent in &parents[idx] {
if !visited[parent] {
stack.push(parent);
}
}
}
nr
}
fn bisect_distance(weight: i64, nr: usize) -> i64 {
weight.min(nr as i64 - weight)
}
pub fn do_find_bisection(oids: &[ObjectId], parents: &[Vec<usize>], find_all: bool) -> Bisection {
let nr = oids.len();
let mut weights: Vec<i64> = vec![0; nr];
let mut counted = 0usize;
for idx in 0..nr {
match parents[idx].len() {
0 => {
weights[idx] = 1;
counted += 1;
}
1 => weights[idx] = -1,
_ => weights[idx] = -2,
}
}
let mut early: Option<usize> = None;
for (idx, weight) in weights.iter_mut().enumerate() {
if *weight != -2 {
continue;
}
*weight = count_distance(idx, parents);
if !find_all && approx_halfway(*weight, nr) {
early = Some(idx);
break;
}
counted += 1;
}
if let Some(idx) = early {
let reaches = weights[idx] as usize;
return Bisection {
picks: vec![idx],
reaches,
weights,
};
}
'fill: while counted < nr {
for idx in 0..nr {
if weights[idx] >= 0 {
continue;
}
let Some(&known) = parents[idx].iter().find(|&&p| weights[p] >= 0) else {
continue;
};
weights[idx] = weights[known] + 1;
counted += 1;
if !find_all && approx_halfway(weights[idx], nr) {
early = Some(idx);
break 'fill;
}
}
}
if let Some(idx) = early {
let reaches = weights[idx] as usize;
return Bisection {
picks: vec![idx],
reaches,
weights,
};
}
if !find_all {
let mut best = 0usize;
let mut best_distance: i64 = -1;
for (idx, &weight) in weights.iter().enumerate() {
let distance = bisect_distance(weight, nr);
if distance > best_distance {
best = idx;
best_distance = distance;
}
}
let reaches = weights[best] as usize;
Bisection {
picks: vec![best],
reaches,
weights,
}
} else {
let mut order: Vec<usize> = (0..nr).collect();
order.sort_by(|&a, &b| {
bisect_distance(weights[b], nr)
.cmp(&bisect_distance(weights[a], nr))
.then_with(|| oids[a].cmp(&oids[b]))
});
let reaches = order.first().map(|&idx| weights[idx] as usize).unwrap_or(0);
Bisection {
picks: order,
reaches,
weights,
}
}
}
pub struct FindBisection {
pub picks: Vec<(ObjectId, i64)>,
pub reaches: usize,
pub all: usize,
}
pub fn find_bisection(
records: &[&CommitRecord],
find_all: bool,
first_parent: bool,
) -> FindBisection {
let oids: Vec<ObjectId> = records.iter().rev().map(|record| record.oid).collect();
let index_by_oid: HashMap<ObjectId, usize> = oids
.iter()
.enumerate()
.map(|(idx, oid)| (*oid, idx))
.collect();
let parents: Vec<Vec<usize>> = records
.iter()
.rev()
.map(|record| {
let mut adjacent = Vec::new();
for parent in &record.parents {
if let Some(&pidx) = index_by_oid.get(parent) {
adjacent.push(pidx);
}
if first_parent {
break;
}
}
adjacent
})
.collect();
let all = oids.len();
if all == 0 {
return FindBisection {
picks: Vec::new(),
reaches: 0,
all: 0,
};
}
let bisection = do_find_bisection(&oids, &parents, find_all);
let picks = if find_all {
bisection
.picks
.iter()
.map(|&idx| (oids[idx], bisect_distance(bisection.weights[idx], all)))
.collect()
} else {
bisection
.picks
.iter()
.map(|&idx| (oids[idx], bisect_distance(bisection.reaches as i64, all)))
.collect()
};
FindBisection {
picks,
reaches: bisection.reaches,
all,
}
}
pub fn estimate_bisect_steps(all: usize) -> usize {
if all < 3 {
return 0;
}
let n = (usize::BITS - 1 - all.leading_zeros()) as usize; let e = 1usize << n;
let x = all - e;
if e < 3 * x { n } else { n - 1 }
}
fn get_prn(count: usize) -> usize {
let count = (count as u32)
.wrapping_mul(1_103_515_245)
.wrapping_add(12_345);
((count / 65_536) % 32_768) as usize
}
fn sqrti(val: i32) -> i32 {
if val == 0 {
return 0;
}
let mut x = val as f32;
loop {
let y = (x + (val as f32) / x) / 2.0;
let d = if y > x { y - x } else { x - y };
x = y;
if d < 0.5 {
break;
}
}
x as i32
}
fn skip_away(list: &[usize], count: usize, oids: &[ObjectId], bad: &ObjectId) -> usize {
let prn = get_prn(count);
let index = (count * prn / 32_768) * sqrti(prn as i32) as usize / sqrti(32_768) as usize;
let mut previous: Option<usize> = None;
for (i, &idx) in list.iter().enumerate() {
if i == index {
if oids[idx] != *bad {
return idx;
}
return previous.unwrap_or(list[0]);
}
previous = Some(idx);
}
list[0]
}
pub enum SkipFilter {
Clean(usize),
Skipped {
pick: Option<usize>,
tried: Vec<usize>,
},
}
pub fn managed_skipped(
picks: &[usize],
oids: &[ObjectId],
skipped: &HashSet<ObjectId>,
bad: &ObjectId,
) -> SkipFilter {
if skipped.is_empty() || picks.is_empty() {
return match picks.first() {
Some(&idx) => SkipFilter::Clean(idx),
None => SkipFilter::Skipped {
pick: None,
tried: Vec::new(),
},
};
}
if !skipped.contains(&oids[picks[0]]) {
return SkipFilter::Clean(picks[0]);
}
let mut tried = Vec::new();
let mut filtered = Vec::new();
for &idx in picks {
if skipped.contains(&oids[idx]) {
tried.push(idx);
} else {
filtered.push(idx);
}
}
if filtered.is_empty() {
return SkipFilter::Skipped { pick: None, tried };
}
let pick = skip_away(&filtered, filtered.len(), oids, bad);
SkipFilter::Skipped {
pick: Some(pick),
tried,
}
}