use std::collections::HashSet;
use std::fmt;
use std::sync::Arc;
use super::super::{
ConflictRepair, RuntimeScalarSlot, RuntimeScalarSlotId, ScalarGroupBinding,
ScalarGroupBindingKind,
};
use super::native::{pull_static_group, pull_static_repair};
use super::types::PanicProviderErrorBoundary;
use super::{
ProviderNormalizationState, ProviderReasonArena, RawProviderCandidate,
ResolvedProviderCandidate, RuntimeConflictRepairProviderBinding,
RuntimeHostProviderErrorBoundary, RuntimeProviderHandle, RuntimeProviderLimits,
RuntimeProviderSlotResolver, RuntimeScalarGroupProviderBinding,
StaticConflictRepairProviderBinding, StaticScalarGroupProviderBinding,
};
use crate::{RepairCandidate, RepairLimits, ScalarCandidate};
pub struct RuntimeProviderRegistry<S> {
groups: Vec<RuntimeScalarGroupProviderBinding<S>>,
repairs: Vec<RuntimeConflictRepairProviderBinding<S>>,
static_groups: Vec<StaticScalarGroupProviderBinding<S>>,
static_repairs: Vec<StaticConflictRepairProviderBinding<S>>,
error_boundary: Arc<dyn RuntimeHostProviderErrorBoundary>,
resolver: Option<RuntimeProviderSlotResolver<S>>,
}
impl<S> Default for RuntimeProviderRegistry<S> {
fn default() -> Self {
Self {
groups: Vec::new(),
repairs: Vec::new(),
static_groups: Vec::new(),
static_repairs: Vec::new(),
error_boundary: Arc::new(PanicProviderErrorBoundary),
resolver: None,
}
}
}
impl<S> Clone for RuntimeProviderRegistry<S> {
fn clone(&self) -> Self {
Self {
groups: self.groups.clone(),
repairs: self.repairs.clone(),
static_groups: self.static_groups.clone(),
static_repairs: self.static_repairs.clone(),
error_boundary: Arc::clone(&self.error_boundary),
resolver: self.resolver.clone(),
}
}
}
impl<S> fmt::Debug for RuntimeProviderRegistry<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeProviderRegistry")
.field("groups", &self.groups)
.field("repairs", &self.repairs)
.field("static_groups", &self.static_groups)
.field("static_repairs", &self.static_repairs)
.field("resolver_bound", &self.resolver.is_some())
.finish()
}
}
impl<S: 'static> RuntimeProviderRegistry<S> {
pub fn new(
groups: Vec<RuntimeScalarGroupProviderBinding<S>>,
repairs: Vec<RuntimeConflictRepairProviderBinding<S>>,
error_boundary: Arc<dyn RuntimeHostProviderErrorBoundary>,
) -> Result<Self, String> {
let mut group_names = HashSet::new();
let mut previous_group_schema_index = None;
for group in &groups {
if group.group_name.is_empty() {
return Err("runtime provider registry has an empty group name".to_string());
}
if previous_group_schema_index.is_some_and(|previous| previous >= group.declared_index)
{
return Err(format!(
"runtime provider group `{}` has non-monotonic declared schema index {}",
group.group_name, group.declared_index
));
}
previous_group_schema_index = Some(group.declared_index);
if !group_names.insert(Arc::clone(&group.group_name)) {
return Err(format!(
"runtime provider registry declares callback group `{}` more than once",
group.group_name
));
}
}
let mut previous_repair_schema_index = None;
for repair in &repairs {
if previous_repair_schema_index
.is_some_and(|previous| previous >= repair.declared_index)
{
return Err(format!(
"runtime repair provider has non-monotonic declared schema index {}",
repair.declared_index
));
}
previous_repair_schema_index = Some(repair.declared_index);
}
Ok(Self {
groups,
repairs,
static_groups: Vec::new(),
static_repairs: Vec::new(),
error_boundary,
resolver: None,
})
}
pub fn is_empty(&self) -> bool {
self.groups.is_empty()
&& self.repairs.is_empty()
&& self.static_groups.is_empty()
&& self.static_repairs.is_empty()
}
pub fn groups(&self) -> &[RuntimeScalarGroupProviderBinding<S>] {
&self.groups
}
pub fn repairs(&self) -> &[RuntimeConflictRepairProviderBinding<S>] {
&self.repairs
}
pub fn static_groups(&self) -> &[StaticScalarGroupProviderBinding<S>] {
&self.static_groups
}
pub fn static_repairs(&self) -> &[StaticConflictRepairProviderBinding<S>] {
&self.static_repairs
}
pub fn group_indices(&self, group_name: &str) -> Vec<usize> {
self.groups
.iter()
.enumerate()
.filter_map(|(index, group)| (group.group_name.as_ref() == group_name).then_some(index))
.collect()
}
pub fn declares_constraint(&self, handle: RuntimeProviderHandle, constraint: &str) -> bool {
match handle {
RuntimeProviderHandle::CallbackRepair(index) => self
.repairs
.get(index)
.unwrap_or_else(|| {
panic!("compiled callback repair provider index {index} no longer exists")
})
.declared_constraints
.iter()
.any(|name| name.as_ref() == constraint),
RuntimeProviderHandle::StaticRepair(index) => {
self.static_repairs
.get(index)
.unwrap_or_else(|| {
panic!("compiled static repair provider index {index} no longer exists")
})
.repair
.constraint_name()
== constraint
}
RuntimeProviderHandle::CallbackGroup(_) | RuntimeProviderHandle::StaticGroup(_) => {
false
}
}
}
pub fn declares_any_constraint(
&self,
handle: RuntimeProviderHandle,
constraints: &[String],
) -> bool {
constraints
.iter()
.any(|constraint| self.declares_constraint(handle, constraint))
}
pub(crate) fn freeze(
&mut self,
scalar_slots: &[RuntimeScalarSlot<S>],
scalar_groups: &[ScalarGroupBinding<S>],
conflict_repairs: &[ConflictRepair<S>],
) -> Result<(), String> {
self.static_groups.clear();
self.static_repairs.clear();
for (declared_index, group) in scalar_groups.iter().enumerate() {
let ScalarGroupBindingKind::Candidates { candidate_provider } = &group.kind else {
continue;
};
self.static_groups.push(StaticScalarGroupProviderBinding {
declared_index,
group_name: group.group_name,
provider: *candidate_provider,
declared_limits: group.limits,
});
}
for (declared_index, repair) in conflict_repairs.iter().enumerate() {
self.static_repairs
.push(StaticConflictRepairProviderBinding {
declared_index,
repair: *repair,
});
}
if self.is_empty() {
return Ok(());
}
self.resolver = Some(RuntimeProviderSlotResolver::new(scalar_slots.to_vec())?);
Ok(())
}
fn resolver(&self) -> &RuntimeProviderSlotResolver<S> {
self.resolver.as_ref().unwrap_or_else(|| {
panic!("runtime provider registry was invoked before descriptor resolution")
})
}
pub fn pull_callback_raw(
&self,
handle: RuntimeProviderHandle,
solution: &S,
limits: RuntimeProviderLimits,
) -> Vec<RawProviderCandidate> {
let _ = self.resolver();
let callback = match handle {
RuntimeProviderHandle::CallbackGroup(index) => self
.groups
.get(index)
.unwrap_or_else(|| {
panic!("compiled callback group provider index {index} no longer exists")
})
.callback
.as_ref(),
RuntimeProviderHandle::CallbackRepair(index) => self
.repairs
.get(index)
.unwrap_or_else(|| {
panic!("compiled callback repair provider index {index} no longer exists")
})
.callback
.as_ref(),
RuntimeProviderHandle::StaticGroup(_) | RuntimeProviderHandle::StaticRepair(_) => {
panic!("static providers must use their concrete pull path")
}
};
callback.pull(solution, limits)
}
pub(crate) fn pull_static_group(
&self,
index: usize,
solution: &S,
value_candidate_limit: Option<usize>,
max_moves_per_step: Option<usize>,
) -> Vec<ScalarCandidate<S>> {
let _ = self.resolver();
let binding = self.static_groups.get(index).unwrap_or_else(|| {
panic!("compiled static group provider index {index} no longer exists")
});
pull_static_group(binding, solution, value_candidate_limit, max_moves_per_step)
}
pub(crate) fn pull_static_repair(
&self,
index: usize,
solution: &S,
limits: RepairLimits,
) -> Vec<RepairCandidate<S>> {
let _ = self.resolver();
let binding = self.static_repairs.get(index).unwrap_or_else(|| {
panic!("compiled static repair provider index {index} no longer exists")
});
pull_static_repair(binding, solution, limits)
}
pub fn normalize_or_raise(
&self,
solution: &S,
raw: Vec<RawProviderCandidate>,
allowed_slots: &[RuntimeScalarSlotId],
state: &mut ProviderNormalizationState,
reasons: &mut ProviderReasonArena,
) -> Vec<ResolvedProviderCandidate<S>> {
match self.resolver().resolve_and_normalize_with_state(
solution,
raw,
allowed_slots,
state,
reasons,
) {
Ok(candidates) => candidates,
Err(error) => self.error_boundary.raise(error),
}
}
pub(crate) fn normalize_static_group(
&self,
solution: &S,
candidates: Vec<ScalarCandidate<S>>,
allowed_slots: &[RuntimeScalarSlotId],
state: &mut ProviderNormalizationState,
reasons: &mut ProviderReasonArena,
) -> Vec<ResolvedProviderCandidate<S>> {
self.resolver()
.resolve_static_group_and_normalize_with_state(
solution,
candidates,
allowed_slots,
state,
reasons,
)
.unwrap_or_else(|error| panic!("{error}"))
}
pub(crate) fn normalize_static_repair(
&self,
solution: &S,
candidates: Vec<RepairCandidate<S>>,
allowed_slots: &[RuntimeScalarSlotId],
state: &mut ProviderNormalizationState,
reasons: &mut ProviderReasonArena,
) -> Vec<ResolvedProviderCandidate<S>> {
self.resolver()
.resolve_static_repair_and_normalize_with_state(
solution,
candidates,
allowed_slots,
state,
reasons,
)
.unwrap_or_else(|error| panic!("{error}"))
}
}