use crate::addressability::{
GltfAnimationAddressabilityBevyAdapterV1, GltfAnimationAddressabilityInventoryV1,
};
use crate::error::PredictionRuleError;
use crate::registry::profiles_v1;
use animsmith_core::{
Applicability, Check, CheckCtx, CheckOutput, CheckSelection, EngineAnimationAddressabilityV1,
EngineFactIdV1, EngineFactStateV1, EngineFactValueV1, EnginePredictionBasisV1,
EnginePredictionBasisV2, EnginePredictionFacetV1, EnginePredictionFacetV2,
EnginePredictionFacetV3, EnginePredictionV1, EnginePredictionV2, EnginePredictionV3,
EvaluationScope, EvaluationScopeCode, LoadedSource, PredictionBasisReferenceV1,
PredictionBasisReferenceV2, PredictionFacetDemandV2, PredictionProvenanceV1,
PredictionProvenanceV2, PredictionProvenanceV3, PredictionRuleAllocationV2,
PredictionUnavailableReasonV1, RAW_SOURCE_V1_MAX_CLIPS, RawSourceBasisReferenceV1,
RawSourceBindingV1, RawSourceBindingV2, RawSourceDomainV1, RawSourceFieldIdV1, RawSourceKeyV1,
SourceFormatV1, SourceSetCoverageStateV1, evaluate_checks,
};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("source animation index {source_clip_index} is outside the V1 limit of {limit} animations")]
pub struct BevyAnimationAssetLabelError {
pub source_clip_index: usize,
pub limit: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum GltfAnimationAddressabilityAdapterError {
#[error(transparent)]
PredictionRule(#[from] PredictionRuleError),
#[error(transparent)]
Evaluation(#[from] animsmith_core::EvaluationError),
#[error(transparent)]
Contract(#[from] crate::GltfAnimationAddressabilityError),
}
pub const ENGINE_ADDRESSABILITY_CHECK_ID: &str = "engine-addressability";
pub const ENGINE_CHECK_IDS_V1: &[&str] = &[ENGINE_ADDRESSABILITY_CHECK_ID];
pub const ENGINE_CHECK_IDS_V2: &[&str] = &[
ENGINE_ADDRESSABILITY_CHECK_ID,
crate::clip_boundary::ENGINE_CLIP_BOUNDARY_CHECK_ID,
crate::unit_scale::ENGINE_UNIT_SCALE_CHECK_ID,
crate::track_support::ENGINE_TRACK_SUPPORT_CHECK_ID,
crate::root_motion::ENGINE_ROOT_MOTION_CHECK_ID,
];
const BEVY_FAMILY: &str = "bevy";
const BEVY_PROFILE_REVISION: u32 = 1;
const BEVY_ENGINE_VERSION: &str = "0.19.0";
const BEVY_IMPORTER: &str = "gltf-asset-loader";
const BEVY_ANIMATION_LABEL_SOURCE: &str = "bevy-gltf-asset-label-0.19.0";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BevyAnimationAssetLabelV1 {
source_clip_index: usize,
display_selector: String,
}
impl BevyAnimationAssetLabelV1 {
pub fn new(source_clip_index: usize) -> Result<Self, BevyAnimationAssetLabelError> {
if source_clip_index >= RAW_SOURCE_V1_MAX_CLIPS {
return Err(BevyAnimationAssetLabelError {
source_clip_index,
limit: RAW_SOURCE_V1_MAX_CLIPS,
});
}
Ok(Self {
source_clip_index,
display_selector: format!("Animation{source_clip_index}"),
})
}
pub const fn source_clip_index(&self) -> usize {
self.source_clip_index
}
pub fn as_str(&self) -> &str {
&self.display_selector
}
}
pub struct EngineAddressabilityCheck<'a> {
source: &'a LoadedSource,
provenance: Option<&'a PredictionProvenanceV1>,
}
pub struct EngineAddressabilityCheckV2<'a> {
source: &'a LoadedSource,
provenance: Option<&'a PredictionProvenanceV2>,
}
pub struct EngineAddressabilityCheckV3<'a> {
source: &'a LoadedSource,
provenance: Option<&'a PredictionProvenanceV3>,
}
impl<'a> EngineAddressabilityCheckV3<'a> {
pub fn new(
source: &'a LoadedSource,
provenance: Option<&'a PredictionProvenanceV3>,
) -> Result<Self, PredictionRuleError> {
if let Some(provenance) = provenance {
provenance
.validate()
.map_err(|_| PredictionRuleError::SourceProvenanceMismatch)?;
let raw_source = RawSourceBindingV2::from_source(
source.source_facts(),
source.exact_source_timing(),
)
.map_err(|_| PredictionRuleError::SourceProvenanceMismatch)?;
if &raw_source != provenance.raw_source()
|| source.dependency_closure() != provenance.dependency_closure()
{
return Err(PredictionRuleError::SourceProvenanceMismatch);
}
let selection = provenance.profile().selection();
if selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& !profiles_v1().iter().any(|profile| {
let selection = profile.selection();
selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& profile.facts_identity() == provenance.profile().facts_identity()
})
{
return Err(PredictionRuleError::FrozenProfileMismatch);
}
}
Ok(Self { source, provenance })
}
}
impl Check for EngineAddressabilityCheckV3<'_> {
fn id(&self) -> &'static str {
ENGINE_ADDRESSABILITY_CHECK_ID
}
fn applicability(&self, _ctx: &CheckCtx<'_>) -> Applicability {
match self.provenance {
Some(provenance)
if is_bevy_gltf_v3(self.source, provenance)
&& !facts_are_complete_and_empty(self.source) =>
{
Applicability::Applicable
}
_ => Applicability::NotApplicable,
}
}
fn evaluate(&self, _ctx: &CheckCtx<'_>) -> CheckOutput {
let Some(provenance) = self.provenance else {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
};
if !is_bevy_gltf_v3(self.source, provenance) || facts_are_complete_and_empty(self.source) {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
evaluate_animation_asset_labels_v3(self.source, provenance)
}
fn prediction_facet_demand_v2(&self, _ctx: &CheckCtx<'_>) -> PredictionFacetDemandV2 {
match self.provenance {
Some(provenance)
if is_bevy_gltf_v3(self.source, provenance)
&& !facts_are_complete_and_empty(self.source) =>
{
if self.source.source_facts().clips().coverage().state()
!= SourceSetCoverageStateV1::Complete
|| matches!(
provenance.settings().clip_coverage().state(),
animsmith_core::ResolvedEngineSettingsCoverageStateV2::Partial
)
{
PredictionFacetDemandV2::Exact(1)
} else {
PredictionFacetDemandV2::Exact(self.source.source_facts().clips().rows().len())
}
}
_ => PredictionFacetDemandV2::Exact(0),
}
}
fn evaluate_with_prediction_allocation_v2(
&self,
_ctx: &CheckCtx<'_>,
allocation: PredictionRuleAllocationV2<'_>,
) -> CheckOutput {
let Some(provenance) = self.provenance else {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
};
if !is_bevy_gltf_v3(self.source, provenance) || facts_are_complete_and_empty(self.source) {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
evaluate_animation_asset_labels_v3_allocated(self.source, provenance, allocation)
}
}
impl<'a> EngineAddressabilityCheckV2<'a> {
pub fn new(
source: &'a LoadedSource,
provenance: Option<&'a PredictionProvenanceV2>,
) -> Result<Self, PredictionRuleError> {
if let Some(provenance) = provenance {
provenance
.validate()
.map_err(|_| PredictionRuleError::SourceProvenanceMismatch)?;
let raw_source = RawSourceBindingV1::from_source(source.source_facts());
if &raw_source != provenance.raw_source()
|| source.dependency_closure() != provenance.dependency_closure()
{
return Err(PredictionRuleError::SourceProvenanceMismatch);
}
let selection = provenance.profile().selection();
if selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& !profiles_v1().iter().any(|profile| {
let selection = profile.selection();
selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& profile.facts_identity() == provenance.profile().facts_identity()
})
{
return Err(PredictionRuleError::FrozenProfileMismatch);
}
}
Ok(Self { source, provenance })
}
}
impl Check for EngineAddressabilityCheckV2<'_> {
fn id(&self) -> &'static str {
ENGINE_ADDRESSABILITY_CHECK_ID
}
fn applicability(&self, _ctx: &CheckCtx<'_>) -> Applicability {
let Some(provenance) = self.provenance else {
return Applicability::NotApplicable;
};
if !is_bevy_gltf_v2(self.source, provenance) || facts_are_complete_and_empty(self.source) {
Applicability::NotApplicable
} else {
Applicability::Applicable
}
}
fn evaluate(&self, _ctx: &CheckCtx<'_>) -> CheckOutput {
let Some(provenance) = self.provenance else {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
};
if !is_bevy_gltf_v2(self.source, provenance) || facts_are_complete_and_empty(self.source) {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
evaluate_animation_asset_labels_v2(self.source, provenance)
}
fn prediction_facet_demand_v2(&self, _ctx: &CheckCtx<'_>) -> PredictionFacetDemandV2 {
match self.provenance {
Some(provenance)
if is_bevy_gltf_v2(self.source, provenance)
&& !facts_are_complete_and_empty(self.source) =>
{
if self.source.source_facts().clips().coverage().state()
!= SourceSetCoverageStateV1::Complete
|| matches!(
provenance.settings().clip_coverage().state(),
animsmith_core::ResolvedEngineSettingsCoverageStateV2::Partial
)
{
PredictionFacetDemandV2::Exact(1)
} else {
PredictionFacetDemandV2::Exact(self.source.source_facts().clips().rows().len())
}
}
_ => PredictionFacetDemandV2::Exact(0),
}
}
fn evaluate_with_prediction_allocation_v2(
&self,
_ctx: &CheckCtx<'_>,
allocation: PredictionRuleAllocationV2<'_>,
) -> CheckOutput {
let Some(provenance) = self.provenance else {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
};
if !is_bevy_gltf_v2(self.source, provenance) || facts_are_complete_and_empty(self.source) {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
evaluate_animation_asset_labels_v2_allocated(self.source, provenance, allocation)
}
}
impl<'a> EngineAddressabilityCheck<'a> {
pub fn new(
source: &'a LoadedSource,
provenance: Option<&'a PredictionProvenanceV1>,
) -> Result<Self, PredictionRuleError> {
if let Some(provenance) = provenance {
let raw_source = RawSourceBindingV1::from_source(source.source_facts());
if &raw_source != provenance.raw_source()
|| source.dependency_closure() != provenance.dependency_closure()
{
return Err(PredictionRuleError::SourceProvenanceMismatch);
}
if is_bevy_tuple(provenance)
&& !profiles_v1().iter().any(|profile| {
let selection = profile.selection();
selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& profile.facts_identity() == provenance.profile().facts_identity()
})
{
return Err(PredictionRuleError::FrozenProfileMismatch);
}
}
Ok(Self { source, provenance })
}
}
impl Check for EngineAddressabilityCheck<'_> {
fn id(&self) -> &'static str {
ENGINE_ADDRESSABILITY_CHECK_ID
}
fn applicability(&self, _ctx: &CheckCtx<'_>) -> Applicability {
borrowed_applicability(self.source, self.provenance)
}
fn evaluate(&self, _ctx: &CheckCtx<'_>) -> CheckOutput {
let Some(provenance) = self.provenance else {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
};
if !is_bevy_gltf(self.source, provenance) || facts_are_complete_and_empty(self.source) {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
evaluate_animation_asset_labels(self.source, provenance)
}
}
pub fn build_bevy_animation_addressability_adapter_v1(
source: &LoadedSource,
inventory: &GltfAnimationAddressabilityInventoryV1,
prediction_provenance: Option<PredictionProvenanceV1>,
ctx: &CheckCtx<'_>,
) -> Result<Option<GltfAnimationAddressabilityBevyAdapterV1>, GltfAnimationAddressabilityAdapterError>
{
let Some(prediction_provenance) = prediction_provenance else {
return Ok(None);
};
let check = EngineAddressabilityCheck::new(source, Some(&prediction_provenance))?;
if !is_bevy_gltf(source, &prediction_provenance) {
return Ok(None);
}
let evaluation = {
let checks: [Box<dyn Check + '_>; 1] = [Box::new(check)];
let mut evaluations = evaluate_checks(ctx, &checks, CheckSelection::All)?;
let evaluation = evaluations
.pop()
.expect("one valid static check catalog produces one evaluation");
debug_assert!(evaluations.is_empty());
evaluation
};
Ok(Some(GltfAnimationAddressabilityBevyAdapterV1::new(
prediction_provenance,
evaluation,
inventory,
)?))
}
fn borrowed_applicability(
source: &LoadedSource,
provenance: Option<&PredictionProvenanceV1>,
) -> Applicability {
let Some(provenance) = provenance else {
return Applicability::NotApplicable;
};
if !is_bevy_gltf(source, provenance) {
return Applicability::NotApplicable;
}
if facts_are_complete_and_empty(source) {
Applicability::NotApplicable
} else {
Applicability::Applicable
}
}
fn facts_are_complete_and_empty(source: &LoadedSource) -> bool {
let clips = source.source_facts().clips();
clips.coverage().state() == SourceSetCoverageStateV1::Complete && clips.rows().is_empty()
}
fn is_bevy_gltf(source: &LoadedSource, provenance: &PredictionProvenanceV1) -> bool {
let facts = source.source_facts();
facts.format() == provenance.source_format()
&& is_bevy_tuple(provenance)
&& matches!(
provenance.source_format(),
SourceFormatV1::GltfJson | SourceFormatV1::Glb
)
&& matches!(
provenance
.profile()
.fact(EngineFactIdV1::AnimationAddressability)
.map(|fact| fact.state()),
Some(EngineFactStateV1::Known(
EngineFactValueV1::AnimationAddressability(
EngineAnimationAddressabilityV1::GltfAssetLabel
)
))
)
&& provenance
.profile()
.source(BEVY_ANIMATION_LABEL_SOURCE)
.is_some()
}
fn is_bevy_gltf_v2(source: &LoadedSource, provenance: &PredictionProvenanceV2) -> bool {
let selection = provenance.profile().selection();
source.source_facts().format() == provenance.source_format()
&& selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& matches!(
provenance
.profile()
.fact(EngineFactIdV1::AnimationAddressability)
.map(|fact| fact.state()),
Some(EngineFactStateV1::Known(
EngineFactValueV1::AnimationAddressability(
EngineAnimationAddressabilityV1::GltfAssetLabel
)
))
)
&& provenance
.profile()
.source(BEVY_ANIMATION_LABEL_SOURCE)
.is_some()
}
fn is_bevy_gltf_v3(source: &LoadedSource, provenance: &PredictionProvenanceV3) -> bool {
let selection = provenance.profile().selection();
source.source_facts().format() == provenance.source_format()
&& selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
&& matches!(
provenance
.profile()
.fact(EngineFactIdV1::AnimationAddressability)
.map(|fact| fact.state()),
Some(EngineFactStateV1::Known(
EngineFactValueV1::AnimationAddressability(
EngineAnimationAddressabilityV1::GltfAssetLabel
)
))
)
&& provenance
.profile()
.source(BEVY_ANIMATION_LABEL_SOURCE)
.is_some()
}
fn is_bevy_tuple(provenance: &PredictionProvenanceV1) -> bool {
let selection = provenance.profile().selection();
selection.family() == BEVY_FAMILY
&& selection.profile_revision() == BEVY_PROFILE_REVISION
&& selection.engine_version() == BEVY_ENGINE_VERSION
&& selection.importer() == BEVY_IMPORTER
}
fn evaluate_animation_asset_labels(
source: &LoadedSource,
provenance: &PredictionProvenanceV1,
) -> CheckOutput {
let facts = source.source_facts();
let inventory_scope =
EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL_INVENTORY);
if facts.format() != provenance.source_format()
|| facts.clips().coverage().state() != SourceSetCoverageStateV1::Complete
{
return unavailable_inventory(provenance, inventory_scope);
}
let mut scopes = Vec::with_capacity(facts.clips().rows().len());
let mut facets = Vec::with_capacity(facts.clips().rows().len());
for clip in facts.clips().rows() {
let source_index = clip.source_clip_index();
let label = BevyAnimationAssetLabelV1::new(source_index)
.expect("loader-valid retained clip indices satisfy the V1 raw-source bound");
let scope = EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL)
.subject(label.as_str().to_owned());
let source_name = RawSourceBasisReferenceV1::from_source(
RawSourceDomainV1::Clip,
RawSourceKeyV1::Clip {
source_clip_index: source_index as u64,
},
RawSourceFieldIdV1::new("source_name.state").expect("static field is valid"),
facts,
)
.expect("loader-valid retained clip rows must resolve their own raw-source witness");
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact id is valid"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static primary-source id is valid"),
PredictionBasisReferenceV1::raw_source(source_name),
])
.expect("three distinct static basis references are valid");
let facet = EnginePredictionFacetV1::available(scope.clone(), basis)
.expect("complete source rows have unique bounded scopes");
scopes.push(scope);
facets.push(facet);
}
if facets.is_empty() {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
let prediction = EnginePredictionV1::new(provenance.identity().clone(), facets)
.expect("complete bounded source rows form one valid canonical prediction");
CheckOutput::from_coverage(Vec::new(), scopes, Vec::new()).with_engine_prediction(prediction)
}
fn unavailable_inventory(
provenance: &PredictionProvenanceV1,
scope: EvaluationScope,
) -> CheckOutput {
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact id is valid"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static primary-source id is valid"),
])
.expect("static basis is nonempty and canonical");
let facet = EnginePredictionFacetV1::required_unavailable(
scope,
basis,
vec![PredictionUnavailableReasonV1::RawSourceIncomplete],
)
.expect("static unavailable facet is valid");
let prediction = EnginePredictionV1::new(provenance.identity().clone(), vec![facet])
.expect("one static facet is valid");
CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new())
.with_engine_prediction(prediction)
}
fn evaluate_animation_asset_labels_v2(
source: &LoadedSource,
provenance: &PredictionProvenanceV2,
) -> CheckOutput {
let facts = source.source_facts();
let inventory_scope =
EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL_INVENTORY);
let settings_overflow = matches!(
provenance.settings().clip_coverage().state(),
animsmith_core::ResolvedEngineSettingsCoverageStateV2::Partial
);
if facts.clips().coverage().state() != SourceSetCoverageStateV1::Complete || settings_overflow {
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact id is valid"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static primary-source id is valid"),
])
.expect("static basis is canonical");
let mut reasons = Vec::new();
if facts.clips().coverage().state() != SourceSetCoverageStateV1::Complete {
reasons.push(animsmith_core::PredictionUnavailableReasonV2::RawSourceIncomplete);
}
if settings_overflow {
reasons.push(animsmith_core::PredictionUnavailableReasonV2::ResolvedSettingsOverflow);
}
let facet = EnginePredictionFacetV2::required_unavailable(inventory_scope, basis, reasons)
.expect("static unavailable V2 facet is valid");
let prediction = EnginePredictionV2::new(provenance.identity().clone(), vec![facet])
.expect("one static V2 facet is valid");
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new())
.with_engine_prediction_v2(prediction);
}
let mut scopes = Vec::with_capacity(facts.clips().rows().len());
let mut facets = Vec::with_capacity(facts.clips().rows().len());
for clip in facts.clips().rows() {
let source_index = clip.source_clip_index();
let label = BevyAnimationAssetLabelV1::new(source_index)
.expect("loader-valid retained clip indices satisfy the V1 raw-source bound");
let scope = EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL)
.subject(label.as_str().to_owned());
let source_name = RawSourceBasisReferenceV1::from_source(
RawSourceDomainV1::Clip,
RawSourceKeyV1::Clip {
source_clip_index: source_index as u64,
},
RawSourceFieldIdV1::new("source_name.state").expect("static field is valid"),
facts,
)
.expect("retained clip rows resolve their raw-source witness");
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact id is valid"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static primary-source id is valid"),
PredictionBasisReferenceV1::raw_source(source_name),
])
.expect("three static basis references are valid");
facets.push(
EnginePredictionFacetV2::available(scope.clone(), basis)
.expect("complete rows form V2 facets"),
);
scopes.push(scope);
}
if facets.is_empty() {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
let prediction = EnginePredictionV2::new(provenance.identity().clone(), facets)
.expect("complete bounded source rows form V2 prediction");
CheckOutput::from_coverage(Vec::new(), scopes, Vec::new()).with_engine_prediction_v2(prediction)
}
fn evaluate_animation_asset_labels_v2_allocated(
source: &LoadedSource,
provenance: &PredictionProvenanceV2,
allocation: PredictionRuleAllocationV2<'_>,
) -> CheckOutput {
let facts = source.source_facts();
let settings_partial = matches!(
provenance.settings().clip_coverage().state(),
animsmith_core::ResolvedEngineSettingsCoverageStateV2::Partial
);
let raw_partial = facts.clips().coverage().state() != SourceSetCoverageStateV1::Complete;
let mut scopes = Vec::with_capacity(allocation.candidate_capacity());
let mut facets = Vec::with_capacity(
allocation.candidate_capacity() + usize::from(allocation.summary_required()),
);
if raw_partial || settings_partial {
if allocation.candidate_capacity() != 0 {
let mut reasons = Vec::new();
if raw_partial {
reasons.push(animsmith_core::PredictionUnavailableReasonV2::RawSourceIncomplete);
}
if settings_partial {
reasons
.push(animsmith_core::PredictionUnavailableReasonV2::ResolvedSettingsOverflow);
}
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static source"),
])
.expect("static basis");
facets.push(
EnginePredictionFacetV2::required_unavailable(
EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL_INVENTORY),
basis,
reasons,
)
.expect("static unavailable V2 facet"),
);
}
} else {
for clip in facts
.clips()
.rows()
.iter()
.take(allocation.candidate_capacity())
{
let source_index = clip.source_clip_index();
let label = BevyAnimationAssetLabelV1::new(source_index)
.expect("loader-valid retained clip indices satisfy the V1 raw-source bound");
let scope = EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL)
.subject(label.as_str().to_owned());
let source_name = RawSourceBasisReferenceV1::from_source(
RawSourceDomainV1::Clip,
RawSourceKeyV1::Clip {
source_clip_index: source_index as u64,
},
RawSourceFieldIdV1::new("source_name.state").expect("static field is valid"),
facts,
)
.expect("retained clip rows resolve their raw-source witness");
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact id is valid"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static primary-source id is valid"),
PredictionBasisReferenceV1::raw_source(source_name),
])
.expect("three static basis references are valid");
facets.push(
EnginePredictionFacetV2::available(scope.clone(), basis)
.expect("complete rows form V2 facets"),
);
scopes.push(scope);
}
}
if allocation.summary_required() {
let basis = EnginePredictionBasisV1::new(vec![
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact"),
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static source"),
])
.expect("static basis");
facets.push(
EnginePredictionFacetV2::required_unavailable(
EvaluationScope::new(EvaluationScopeCode::custom(
"engine-addressability:facet-budget",
)),
basis,
vec![animsmith_core::PredictionUnavailableReasonV2::FacetBudgetExceeded],
)
.expect("budget summary facet"),
);
}
if facets.is_empty() {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
CheckOutput::from_coverage(Vec::new(), scopes, Vec::new()).with_engine_prediction_v2(
EnginePredictionV2::new(provenance.identity().clone(), facets)
.expect("allocated V2 facets"),
)
}
fn evaluate_animation_asset_labels_v3(
source: &LoadedSource,
provenance: &PredictionProvenanceV3,
) -> CheckOutput {
let raw_partial =
source.source_facts().clips().coverage().state() != SourceSetCoverageStateV1::Complete;
let settings_partial = matches!(
provenance.settings().clip_coverage().state(),
animsmith_core::ResolvedEngineSettingsCoverageStateV2::Partial
);
let candidate_capacity = if raw_partial || settings_partial {
1
} else {
source.source_facts().clips().rows().len()
};
evaluate_animation_asset_labels_v3_with_limits(source, provenance, candidate_capacity, false)
}
fn evaluate_animation_asset_labels_v3_allocated(
source: &LoadedSource,
provenance: &PredictionProvenanceV3,
allocation: PredictionRuleAllocationV2<'_>,
) -> CheckOutput {
evaluate_animation_asset_labels_v3_with_limits(
source,
provenance,
allocation.candidate_capacity(),
allocation.summary_required(),
)
}
fn evaluate_animation_asset_labels_v3_with_limits(
source: &LoadedSource,
provenance: &PredictionProvenanceV3,
candidate_capacity: usize,
summary_required: bool,
) -> CheckOutput {
let facts = source.source_facts();
let settings_partial = matches!(
provenance.settings().clip_coverage().state(),
animsmith_core::ResolvedEngineSettingsCoverageStateV2::Partial
);
let raw_partial = facts.clips().coverage().state() != SourceSetCoverageStateV1::Complete;
let mut scopes = Vec::with_capacity(candidate_capacity);
let mut facets = Vec::with_capacity(candidate_capacity + usize::from(summary_required));
if raw_partial || settings_partial {
if candidate_capacity != 0 {
let mut reasons = Vec::new();
if raw_partial {
reasons.push(animsmith_core::PredictionUnavailableReasonV2::RawSourceIncomplete);
}
if settings_partial {
reasons
.push(animsmith_core::PredictionUnavailableReasonV2::ResolvedSettingsOverflow);
}
facets.push(
EnginePredictionFacetV3::required_unavailable(
EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL_INVENTORY),
addressability_basis_v3(facts, None),
reasons,
)
.expect("static unavailable V3 facet"),
);
}
} else {
for clip in facts.clips().rows().iter().take(candidate_capacity) {
let source_index = clip.source_clip_index();
let label = BevyAnimationAssetLabelV1::new(source_index)
.expect("loader-valid retained clip indices satisfy the V1 raw-source bound");
let scope = EvaluationScope::new(EvaluationScopeCode::ANIMATION_ASSET_LABEL)
.subject(label.as_str().to_owned());
facets.push(
EnginePredictionFacetV3::available(
scope.clone(),
addressability_basis_v3(facts, Some(source_index)),
)
.expect("complete rows form V3 facets"),
);
scopes.push(scope);
}
}
if summary_required {
facets.push(
EnginePredictionFacetV3::required_unavailable(
EvaluationScope::new(EvaluationScopeCode::custom(
"engine-addressability:facet-budget",
)),
addressability_basis_v3(facts, None),
vec![animsmith_core::PredictionUnavailableReasonV2::FacetBudgetExceeded],
)
.expect("budget summary V3 facet"),
);
}
if facets.is_empty() {
return CheckOutput::from_coverage(Vec::new(), Vec::new(), Vec::new());
}
CheckOutput::from_coverage(Vec::new(), scopes, Vec::new()).with_engine_prediction_v3(
EnginePredictionV3::new(provenance.identity().clone(), facets)
.expect("allocated V3 facets"),
)
}
fn addressability_basis_v3(
facts: animsmith_core::SourceFactsViewV1<'_>,
source_index: Option<usize>,
) -> EnginePredictionBasisV2 {
let mut references = vec![
PredictionBasisReferenceV2::v1(
PredictionBasisReferenceV1::profile_fact("animation_addressability")
.expect("static fact id is valid"),
),
PredictionBasisReferenceV2::v1(
PredictionBasisReferenceV1::primary_source(BEVY_ANIMATION_LABEL_SOURCE)
.expect("static primary-source id is valid"),
),
];
if let Some(source_index) = source_index {
let source_name = RawSourceBasisReferenceV1::from_source(
RawSourceDomainV1::Clip,
RawSourceKeyV1::Clip {
source_clip_index: source_index as u64,
},
RawSourceFieldIdV1::new("source_name.state").expect("static field is valid"),
facts,
)
.expect("retained clip rows resolve their raw-source witness");
references.push(PredictionBasisReferenceV2::v1(
PredictionBasisReferenceV1::raw_source(source_name),
));
}
EnginePredictionBasisV2::new(references).expect("static addressability basis is canonical")
}