pipecircuit 0.0.15

Correct. Robust. Fast. Portable.
Documentation
use super::{SlotLocation, StableSlot, StableSlots, StableSlotsInconsistency};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum StableSlotRowRelocation {
    Stable,
    Relocated {
        slot: StableSlot,
        from: usize,
        to: usize,
    },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum StableSlotRemoveOutcome {
    Removed {
        slot: StableSlot,
        relocation: StableSlotRowRelocation,
    },
}

pub struct PreparedStableSlotRemove<'slots> {
    slots: &'slots mut StableSlots,
    row: usize,
    relocated_row: u32,
    removed_slot: StableSlot,
    removed_slot_index: usize,
    relocated: Option<(StableSlot, usize)>,
    last_row: usize,
}

impl StableSlots {
    pub fn try_prepare_remove(
        &mut self,
        row: usize,
    ) -> Result<PreparedStableSlotRemove<'_>, StableSlotsInconsistency> {
        let last_row = self
            .row_to_slot
            .len()
            .checked_sub(1)
            .ok_or(StableSlotsInconsistency::RowUnavailable { row })?;
        let removed_slot = self
            .row_to_slot
            .get(row)
            .copied()
            .ok_or(StableSlotsInconsistency::RowUnavailable { row })?;
        let removed_slot_index = self.validate_location(removed_slot, row)?;
        let relocated_row =
            u32::try_from(row).map_err(|_| StableSlotsInconsistency::RowUnrepresentable { row })?;
        let relocated = if row == last_row {
            None
        } else {
            let moved_slot = self
                .row_to_slot
                .get(last_row)
                .copied()
                .ok_or(StableSlotsInconsistency::RowUnavailable { row: last_row })?;
            if moved_slot == removed_slot {
                return Err(StableSlotsInconsistency::DuplicateSlot { slot: moved_slot });
            }
            let moved_slot_index = self.validate_location(moved_slot, last_row)?;
            Some((moved_slot, moved_slot_index))
        };
        Ok(PreparedStableSlotRemove {
            slots: self,
            row,
            relocated_row,
            removed_slot,
            removed_slot_index,
            relocated,
            last_row,
        })
    }

    pub fn remove(
        &mut self,
        row: usize,
    ) -> Result<StableSlotRemoveOutcome, StableSlotsInconsistency> {
        Ok(self.try_prepare_remove(row)?.commit())
    }
}

impl PreparedStableSlotRemove<'_> {
    pub const fn slot(&self) -> StableSlot {
        self.removed_slot
    }

    #[allow(
        clippy::indexing_slicing,
        reason = "preparation validated distinct slot indexes and dense rows"
    )]
    pub fn commit(self) -> StableSlotRemoveOutcome {
        if let Some((_, moved_slot_index)) = self.relocated {
            self.slots.slot_locations[moved_slot_index] =
                SlotLocation::Occupied(self.relocated_row);
        }
        self.slots.slot_locations[self.removed_slot_index] =
            SlotLocation::Free(self.slots.first_free);
        self.slots.first_free = Some(self.removed_slot);
        self.slots.row_to_slot.swap_remove(self.row);
        let relocation = match self.relocated {
            Some((slot, _)) => StableSlotRowRelocation::Relocated {
                slot,
                from: self.last_row,
                to: self.row,
            },
            None => StableSlotRowRelocation::Stable,
        };
        StableSlotRemoveOutcome::Removed {
            slot: self.removed_slot,
            relocation,
        }
    }
}