1use crate::types::UnrealSampleRateV2;
2use crate::{
3 AnimationAddressability, BakeOrExtract, ConversionControl, DefaultStatus, EngineProfile,
4 EngineProfileV2, FactState, FactValue, ForwardAxis, Handedness, ImportHandling, LinearUnit,
5 ResolvedClipCoverageV2, ResolvedProfile, ResolvedProfileSettingsV2, ResolvedProfileV2,
6 ResolvedSettingOriginV2, RootMotionAddressability, SettingApplicability, SettingDefaultV2,
7 SettingDomainV2, SettingId, SettingIdV2, SettingScope, SettingValue, SettingValueV2,
8 TargetAddressability, UpAxis,
9};
10use animsmith_core::engine_contract::{
11 EngineAnimationAddressabilityV1, EngineBakeOrExtractV1, EngineClipSettingsV1,
12 EngineContractError, EngineConversionControlV1, EngineCoordinateBasisV1, EngineDefaultStatusV1,
13 EngineFactIdV1, EngineFactStateV1, EngineFactValueV1, EngineForwardAxisV1, EngineHandednessV1,
14 EngineImportHandlingV1, EngineLinearUnitV1, EnginePrimarySourceV1, EngineProfileFactV1,
15 EngineProfileSelectionV1, EngineRootMotionAddressabilityV1, EngineSettingApplicabilityV1,
16 EngineSettingDescriptorV1, EngineSettingDomainV1, EngineSettingIdV1, EngineSettingRowV1,
17 EngineSettingScopeV1, EngineSettingValueV1, EngineTargetAddressabilityV1, EngineUpAxisV1,
18 ResolvedEngineProfileV1, ResolvedEngineSettingsCoverageV2, ResolvedEngineSettingsV1,
19 ResolvedEngineSettingsV2, ResolvedEngineSettingsWorkV2,
20};
21use animsmith_core::engine_contract::{
22 EngineClipSettingsV3 as CoreClipSettingsV3, EnginePrimarySourceV2 as CorePrimarySourceV2,
23 EngineSampleRateV2 as CoreSampleRateV2, EngineSettingDescriptorV2 as CoreSettingDescriptorV2,
24 EngineSettingDomainV2 as CoreSettingDomainV2, EngineSettingIdV2 as CoreSettingIdV2,
25 EngineSettingRowV3, EngineSettingValueOriginV3, EngineSettingValueV2 as CoreSettingValueV2,
26 ResolvedEngineProfileV2 as CoreEngineProfileV2,
27 ResolvedEngineSettingsCoverageV2 as CoreSettingsCoverageV2,
28 ResolvedEngineSettingsV3 as CoreSettingsV3, ResolvedEngineSettingsWorkV2 as CoreSettingsWorkV2,
29};
30use animsmith_core::{InputIdentity, SourceFormatV1};
31use std::collections::BTreeMap;
32
33pub(crate) fn facts_identity(profile: &EngineProfile) -> InputIdentity {
34 project_profile(profile)
35 .expect("the frozen engine registry must project into its core-owned V1 contract")
36 .facts_identity()
37 .clone()
38}
39
40pub(crate) fn settings_identity<'a>(
41 profile: &EngineProfile,
42 document: &BTreeMap<SettingId, SettingValue>,
43 clips: impl IntoIterator<Item = (&'a str, &'a BTreeMap<SettingId, SettingValue>)>,
44) -> Result<InputIdentity, EngineContractError> {
45 let profile = project_profile(profile)?;
46 Ok(project_settings(&profile, document, clips)?
47 .settings_identity()
48 .clone())
49}
50
51pub(crate) fn project_resolved_profile(
52 resolved: &ResolvedProfile,
53) -> Result<(ResolvedEngineProfileV1, ResolvedEngineSettingsV1), EngineContractError> {
54 let profile = project_profile(resolved.profile())?;
55 let settings = project_settings(
56 &profile,
57 resolved.document_settings(),
58 resolved
59 .clip_settings()
60 .iter()
61 .map(|clip| (clip.clip_name(), clip.settings())),
62 )?;
63 Ok((profile, settings))
64}
65
66pub(crate) fn project_resolved_profile_v2(
68 resolved: &ResolvedProfileV2,
69) -> Result<(ResolvedEngineProfileV1, ResolvedEngineSettingsV2), EngineContractError> {
70 let profile = project_profile(resolved.profile())?;
71 let coverage = match resolved.clip_coverage() {
72 ResolvedClipCoverageV2::Complete => ResolvedEngineSettingsCoverageV2::complete(),
73 ResolvedClipCoverageV2::Partial { .. } => {
74 ResolvedEngineSettingsCoverageV2::actual_clip_rows_exceeded()
75 }
76 };
77 let work = resolved.work();
78 let settings = ResolvedEngineSettingsV2::new(
79 &profile,
80 resolved
81 .document_settings()
82 .iter()
83 .map(|(id, value)| EngineSettingRowV1::new(setting_id(*id), setting_value(value)))
84 .collect(),
85 resolved
86 .clip_settings()
87 .iter()
88 .map(|clip| {
89 EngineClipSettingsV1::new(
90 clip.clip_name(),
91 clip.settings()
92 .iter()
93 .map(|(id, value)| {
94 EngineSettingRowV1::new(setting_id(*id), setting_value(value))
95 })
96 .collect(),
97 )
98 })
99 .collect::<Result<Vec<_>, _>>()?,
100 coverage,
101 ResolvedEngineSettingsWorkV2::new(
102 work.actual_clip_rows_inspected(),
103 work.materialized_clip_rows(),
104 work.retained_clip_rows(),
105 ),
106 )?;
107 Ok((profile, settings))
108}
109
110pub fn project_engine_profile_v2(
113 profile: &EngineProfileV2,
114) -> Result<CoreEngineProfileV2, EngineContractError> {
115 let selection = profile.selection();
116 CoreEngineProfileV2::new(
117 EngineProfileSelectionV1::new(
118 selection.family(),
119 selection.profile_revision(),
120 selection.engine_version(),
121 selection.importer(),
122 )?,
123 profile.profile_urn(),
124 profile.facts().to_vec(),
125 profile
126 .setting_descriptors()
127 .iter()
128 .map(|descriptor| {
129 CoreSettingDescriptorV2::new(
130 setting_id_v2(descriptor.id()),
131 match descriptor.scope() {
132 SettingScope::Document => EngineSettingScopeV1::Document,
133 SettingScope::Clip => EngineSettingScopeV1::Clip,
134 },
135 match descriptor.domain() {
136 SettingDomainV2::Boolean => CoreSettingDomainV2::Boolean,
137 SettingDomainV2::BakeOrExtract => CoreSettingDomainV2::BakeOrExtract,
138 SettingDomainV2::SourceTransformPath => {
139 CoreSettingDomainV2::SourceTransformPath
140 }
141 SettingDomainV2::AnimationType | SettingDomainV2::AvatarSetup => {
142 CoreSettingDomainV2::Token
143 }
144 SettingDomainV2::LoadMeshesState | SettingDomainV2::HandlerEnvironment => {
145 CoreSettingDomainV2::Token
146 }
147 SettingDomainV2::PositiveInteger => CoreSettingDomainV2::PositiveInteger,
148 SettingDomainV2::SampleRate => CoreSettingDomainV2::SampleRate,
149 },
150 profile.accepted_inputs().to_vec(),
151 match descriptor.default() {
152 SettingDefaultV2::RequiredExplicit => None,
153 SettingDefaultV2::Verified(value) => Some(setting_value_v2(value)),
154 },
155 )
156 })
157 .collect::<Result<Vec<_>, _>>()?,
158 profile
159 .sources()
160 .iter()
161 .map(|source| {
162 CorePrimarySourceV2::new(
163 source.id(),
164 source.target_version(),
165 source.url(),
166 source.verified_on(),
167 source.supported_facts().to_vec(),
168 source
169 .supported_settings()
170 .iter()
171 .copied()
172 .map(setting_id_v2)
173 .collect(),
174 )
175 })
176 .collect::<Result<Vec<_>, _>>()?,
177 )
178}
179
180pub fn project_resolved_engine_settings_v3(
183 resolved: &ResolvedProfileSettingsV2,
184) -> Result<(CoreEngineProfileV2, CoreSettingsV3), EngineContractError> {
185 let profile = project_engine_profile_v2(resolved.profile())?;
186 let document_settings = resolved
187 .document_settings()
188 .iter()
189 .map(|(id, resolved)| {
190 EngineSettingRowV3::new(
191 setting_id_v2(*id),
192 setting_value_v2(resolved.value()),
193 match resolved.origin() {
194 ResolvedSettingOriginV2::ExplicitConfig => {
195 EngineSettingValueOriginV3::ExplicitConfig
196 }
197 ResolvedSettingOriginV2::ProfileDefault => {
198 EngineSettingValueOriginV3::ProfileDefault
199 }
200 },
201 )
202 })
203 .collect::<Vec<_>>();
204 let clips = resolved
205 .clip_settings()
206 .iter()
207 .map(|clip| {
208 CoreClipSettingsV3::new(
209 clip.clip_ordinal(),
210 clip.clip_name(),
211 clip.settings()
212 .iter()
213 .map(|(id, resolved)| {
214 EngineSettingRowV3::new(
215 setting_id_v2(*id),
216 setting_value_v2(resolved.value()),
217 match resolved.origin() {
218 ResolvedSettingOriginV2::ExplicitConfig => {
219 EngineSettingValueOriginV3::ExplicitConfig
220 }
221 ResolvedSettingOriginV2::ProfileDefault => {
222 EngineSettingValueOriginV3::ProfileDefault
223 }
224 },
225 )
226 })
227 .collect(),
228 )
229 })
230 .collect::<Result<Vec<_>, _>>()?;
231 let coverage = match resolved.clip_coverage() {
232 ResolvedClipCoverageV2::Complete => CoreSettingsCoverageV2::complete(),
233 ResolvedClipCoverageV2::Partial { .. } => {
234 CoreSettingsCoverageV2::actual_clip_rows_exceeded()
235 }
236 };
237 let work = resolved.work();
238 let settings = CoreSettingsV3::new(
239 &profile,
240 resolved.source_format(),
241 document_settings,
242 clips,
243 coverage,
244 CoreSettingsWorkV2::new(
245 work.actual_clip_rows_inspected(),
246 work.materialized_clip_rows(),
247 work.retained_clip_rows(),
248 ),
249 )?;
250 Ok((profile, settings))
251}
252
253fn project_profile(
254 profile: &EngineProfile,
255) -> Result<ResolvedEngineProfileV1, EngineContractError> {
256 let selection = profile.selection();
257 ResolvedEngineProfileV1::new(
258 EngineProfileSelectionV1::new(
259 selection.family(),
260 selection.profile_revision(),
261 selection.engine_version(),
262 selection.importer(),
263 )?,
264 profile.fact_bundle_urn(),
265 profile
266 .facts()
267 .iter()
268 .map(|fact| EngineProfileFactV1::new(fact_id(fact.id()), fact_state(fact.state())))
269 .collect(),
270 profile
271 .setting_descriptors()
272 .iter()
273 .map(|descriptor| {
274 EngineSettingDescriptorV1::new(
275 setting_id(descriptor.id()),
276 match descriptor.scope() {
277 SettingScope::Document => EngineSettingScopeV1::Document,
278 SettingScope::Clip => EngineSettingScopeV1::Clip,
279 },
280 match descriptor.domain() {
281 crate::SettingDomain::Boolean => EngineSettingDomainV1::Boolean,
282 crate::SettingDomain::BakeOrExtract => EngineSettingDomainV1::BakeOrExtract,
283 crate::SettingDomain::SourceTransformPath => {
284 EngineSettingDomainV1::SourceTransformPath
285 }
286 },
287 match descriptor.applicability() {
288 SettingApplicability::Applicable => {
289 EngineSettingApplicabilityV1::Applicable
290 }
291 SettingApplicability::NotApplicable => {
292 EngineSettingApplicabilityV1::NotApplicable
293 }
294 },
295 match descriptor.default_status() {
296 DefaultStatus::RequiredWithoutDefault => {
297 EngineDefaultStatusV1::RequiredWithoutDefault
298 }
299 DefaultStatus::NotApplicable => EngineDefaultStatusV1::NotApplicable,
300 },
301 )
302 })
303 .collect(),
304 profile
305 .sources()
306 .iter()
307 .map(|source| {
308 EnginePrimarySourceV1::new(
309 source.id(),
310 source.target_version(),
311 source.url(),
312 source.verified_on(),
313 source
314 .supported_facts()
315 .iter()
316 .copied()
317 .map(fact_id)
318 .collect(),
319 source
320 .supported_settings()
321 .iter()
322 .copied()
323 .map(setting_id)
324 .collect(),
325 )
326 })
327 .collect::<Result<Vec<_>, _>>()?,
328 )
329}
330
331fn project_settings<'a>(
332 profile: &ResolvedEngineProfileV1,
333 document: &BTreeMap<SettingId, SettingValue>,
334 clips: impl IntoIterator<Item = (&'a str, &'a BTreeMap<SettingId, SettingValue>)>,
335) -> Result<ResolvedEngineSettingsV1, EngineContractError> {
336 ResolvedEngineSettingsV1::new(
337 profile,
338 document
339 .iter()
340 .map(|(id, value)| EngineSettingRowV1::new(setting_id(*id), setting_value(value)))
341 .collect(),
342 clips
343 .into_iter()
344 .map(|(clip_name, values)| {
345 EngineClipSettingsV1::new(
346 clip_name,
347 values
348 .iter()
349 .map(|(id, value)| {
350 EngineSettingRowV1::new(setting_id(*id), setting_value(value))
351 })
352 .collect(),
353 )
354 })
355 .collect::<Result<Vec<_>, _>>()?,
356 )
357}
358
359pub(crate) fn validate_clip_settings(
361 clip_name: &str,
362 values: &BTreeMap<SettingId, SettingValue>,
363) -> Result<(), EngineContractError> {
364 EngineClipSettingsV1::new(
365 clip_name,
366 values
367 .iter()
368 .map(|(id, value)| EngineSettingRowV1::new(setting_id(*id), setting_value(value)))
369 .collect(),
370 )?;
371 Ok(())
372}
373
374fn fact_state(state: &FactState) -> EngineFactStateV1 {
375 match state {
376 FactState::Known(value) => EngineFactStateV1::Known(fact_value(value)),
377 FactState::Unknown => EngineFactStateV1::Unknown,
378 FactState::NotApplicable => EngineFactStateV1::NotApplicable,
379 }
380}
381
382fn fact_value(value: &FactValue) -> EngineFactValueV1 {
383 match value {
384 FactValue::AcceptedFormats(formats) => EngineFactValueV1::AcceptedFormats(formats.clone()),
385 FactValue::AnimationAddressability(AnimationAddressability::GltfAssetLabel) => {
386 EngineFactValueV1::AnimationAddressability(
387 EngineAnimationAddressabilityV1::GltfAssetLabel,
388 )
389 }
390 FactValue::CoordinateBasis(value) => {
391 EngineFactValueV1::CoordinateBasis(EngineCoordinateBasisV1 {
392 handedness: match value.handedness {
393 Handedness::Left => EngineHandednessV1::Left,
394 Handedness::Right => EngineHandednessV1::Right,
395 },
396 up_axis: match value.up_axis {
397 UpAxis::X => EngineUpAxisV1::X,
398 UpAxis::Y => EngineUpAxisV1::Y,
399 UpAxis::Z => EngineUpAxisV1::Z,
400 },
401 forward_axis: match value.forward_axis {
402 ForwardAxis::PositiveX => EngineForwardAxisV1::PositiveX,
403 ForwardAxis::NegativeX => EngineForwardAxisV1::NegativeX,
404 ForwardAxis::PositiveY => EngineForwardAxisV1::PositiveY,
405 ForwardAxis::NegativeY => EngineForwardAxisV1::NegativeY,
406 ForwardAxis::PositiveZ => EngineForwardAxisV1::PositiveZ,
407 ForwardAxis::NegativeZ => EngineForwardAxisV1::NegativeZ,
408 },
409 })
410 }
411 FactValue::LinearUnit(value) => EngineFactValueV1::LinearUnit(match value {
412 LinearUnit::Metre => EngineLinearUnitV1::Metre,
413 LinearUnit::Centimetre => EngineLinearUnitV1::Centimetre,
414 }),
415 FactValue::ConversionControl(value) => EngineFactValueV1::ConversionControl(match value {
416 ConversionControl::ProfileSetting(id) => {
417 EngineConversionControlV1::ProfileSetting(setting_id(*id))
418 }
419 ConversionControl::ImporterOption => EngineConversionControlV1::ImporterOption,
420 }),
421 FactValue::Boolean(value) => EngineFactValueV1::Boolean(*value),
422 FactValue::ImportHandling(value) => EngineFactValueV1::ImportHandling(match value {
423 ImportHandling::Preserved => EngineImportHandlingV1::Preserved,
424 ImportHandling::Converted => EngineImportHandlingV1::Converted,
425 ImportHandling::Discarded => EngineImportHandlingV1::Discarded,
426 ImportHandling::Unsupported => EngineImportHandlingV1::Unsupported,
427 }),
428 FactValue::TargetAddressability(TargetAddressability::NamePathDerivedId) => {
429 EngineFactValueV1::TargetAddressability(EngineTargetAddressabilityV1::NamePathDerivedId)
430 }
431 FactValue::RootMotionAddressability(value) => {
432 EngineFactValueV1::RootMotionAddressability(match value {
433 RootMotionAddressability::ExactSourceTransformPath => {
434 EngineRootMotionAddressabilityV1::ExactSourceTransformPath
435 }
436 RootMotionAddressability::HumanoidAvatarBody => {
437 EngineRootMotionAddressabilityV1::HumanoidAvatarBody
438 }
439 })
440 }
441 }
442}
443
444fn setting_value(value: &SettingValue) -> EngineSettingValueV1 {
445 match value {
446 SettingValue::Boolean(value) => EngineSettingValueV1::Boolean(*value),
447 SettingValue::BakeOrExtract(value) => EngineSettingValueV1::BakeOrExtract(match value {
448 BakeOrExtract::Bake => EngineBakeOrExtractV1::Bake,
449 BakeOrExtract::Extract => EngineBakeOrExtractV1::Extract,
450 }),
451 SettingValue::SourceTransformPath(value) => {
452 EngineSettingValueV1::SourceTransformPath(value.clone())
453 }
454 }
455}
456
457const fn fact_id(id: crate::FactId) -> EngineFactIdV1 {
458 match id {
459 crate::FactId::AcceptedInputs => EngineFactIdV1::AcceptedInputs,
460 crate::FactId::AnimationAddressability => EngineFactIdV1::AnimationAddressability,
461 crate::FactId::TargetCoordinateBasis => EngineFactIdV1::TargetCoordinateBasis,
462 crate::FactId::TargetLinearUnit => EngineFactIdV1::TargetLinearUnit,
463 crate::FactId::UnitConversionControl => EngineFactIdV1::UnitConversionControl,
464 crate::FactId::AxisConversionControl => EngineFactIdV1::AxisConversionControl,
465 crate::FactId::ExactAxisConversion => EngineFactIdV1::ExactAxisConversion,
466 crate::FactId::ResultingHierarchyScale => EngineFactIdV1::ResultingHierarchyScale,
467 crate::FactId::WholeEndFrameRequired => EngineFactIdV1::WholeEndFrameRequired,
468 crate::FactId::AnimationChannelHandling => EngineFactIdV1::AnimationChannelHandling,
469 crate::FactId::ExtensionHandling => EngineFactIdV1::ExtensionHandling,
470 crate::FactId::ConstructHandling => EngineFactIdV1::ConstructHandling,
471 crate::FactId::AnimationTargetAddressability => {
472 EngineFactIdV1::AnimationTargetAddressability
473 }
474 crate::FactId::RootMotionAddressability => EngineFactIdV1::RootMotionAddressability,
475 }
476}
477
478const fn setting_id(id: SettingId) -> EngineSettingIdV1 {
479 match id {
480 SettingId::ConvertUnits => EngineSettingIdV1::ConvertUnits,
481 SettingId::BakeAxisConversion => EngineSettingIdV1::BakeAxisConversion,
482 SettingId::RootMotionSource => EngineSettingIdV1::RootMotionSource,
483 SettingId::RootRotation => EngineSettingIdV1::RootRotation,
484 SettingId::RootPositionY => EngineSettingIdV1::RootPositionY,
485 SettingId::RootPositionXz => EngineSettingIdV1::RootPositionXz,
486 }
487}
488
489const fn setting_id_v2(id: SettingIdV2) -> CoreSettingIdV2 {
490 match id {
491 SettingIdV2::AnimationType => CoreSettingIdV2::AnimationType,
492 SettingIdV2::AvatarSetup => CoreSettingIdV2::AvatarSetup,
493 SettingIdV2::ImportAnimation => CoreSettingIdV2::ImportAnimation,
494 SettingIdV2::RootMotionSource => CoreSettingIdV2::RootMotionSource,
495 SettingIdV2::RootRotation => CoreSettingIdV2::RootRotation,
496 SettingIdV2::RootPositionY => CoreSettingIdV2::RootPositionY,
497 SettingIdV2::RootPositionXz => CoreSettingIdV2::RootPositionXz,
498 SettingIdV2::RotateSceneEntity => CoreSettingIdV2::RotateSceneEntity,
499 SettingIdV2::RotateMeshes => CoreSettingIdV2::RotateMeshes,
500 SettingIdV2::LoadMeshes => CoreSettingIdV2::LoadMeshes,
501 SettingIdV2::ExtensionHandlerEnvironment => CoreSettingIdV2::ExtensionHandlerEnvironment,
502 SettingIdV2::BevyAnimationFeature => CoreSettingIdV2::BevyAnimationFeature,
503 SettingIdV2::LoadAnimations => CoreSettingIdV2::LoadAnimations,
504 SettingIdV2::AnimationFps => CoreSettingIdV2::AnimationFps,
505 SettingIdV2::AnimationTrimming => CoreSettingIdV2::AnimationTrimming,
506 SettingIdV2::SampleRate => CoreSettingIdV2::SampleRate,
507 }
508}
509
510fn setting_value_v2(value: &SettingValueV2) -> CoreSettingValueV2 {
511 match value {
512 SettingValueV2::Boolean(value) => CoreSettingValueV2::Boolean(*value),
513 SettingValueV2::BakeOrExtract(value) => CoreSettingValueV2::BakeOrExtract(match value {
514 crate::BakeOrExtract::Bake => {
515 animsmith_core::engine_contract::EngineBakeOrExtractV1::Bake
516 }
517 crate::BakeOrExtract::Extract => {
518 animsmith_core::engine_contract::EngineBakeOrExtractV1::Extract
519 }
520 }),
521 SettingValueV2::SourceTransformPath(value) => {
522 CoreSettingValueV2::SourceTransformPath(value.clone())
523 }
524 SettingValueV2::AnimationType(value) => CoreSettingValueV2::Token(value.as_str().into()),
525 SettingValueV2::AvatarSetup(value) => CoreSettingValueV2::Token(value.as_str().into()),
526 SettingValueV2::LoadMeshesState(value) => CoreSettingValueV2::Token(
527 match value {
528 crate::BevyLoadMeshesStateV2::Empty => "empty",
529 crate::BevyLoadMeshesStateV2::Nonempty => "nonempty",
530 }
531 .into(),
532 ),
533 SettingValueV2::HandlerEnvironment(value) => {
534 CoreSettingValueV2::Token(value.as_str().into())
535 }
536 SettingValueV2::PositiveInteger(value) => CoreSettingValueV2::PositiveInteger(*value),
537 SettingValueV2::SampleRate(value) => CoreSettingValueV2::SampleRate(match value {
538 UnrealSampleRateV2::Default30 => CoreSampleRateV2::Default30,
539 UnrealSampleRateV2::SourceDetermined => CoreSampleRateV2::SourceDetermined,
540 UnrealSampleRateV2::CustomHz(value) => CoreSampleRateV2::CustomHz(*value),
541 }),
542 }
543}
544
545pub(crate) const fn format_name(format: SourceFormatV1) -> &'static str {
546 match format {
547 SourceFormatV1::GltfJson => "gltf_json",
548 SourceFormatV1::Glb => "glb",
549 SourceFormatV1::Fbx => "fbx",
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556 use crate::{
557 FactId, ProfileFact, SettingDescriptor, SettingDomainV2, SettingValueV2,
558 types::UnrealSampleRateV2,
559 };
560
561 #[test]
562 fn advice_setting_values_map_to_the_closed_core_vocabulary_and_bounds() {
563 assert_eq!(
564 setting_value_v2(&SettingValueV2::PositiveInteger(120)),
565 CoreSettingValueV2::PositiveInteger(120)
566 );
567 assert!(
568 !SettingValueV2::PositiveInteger(0).matches_domain(SettingDomainV2::PositiveInteger)
569 );
570 assert!(
571 !SettingValueV2::PositiveInteger(121).matches_domain(SettingDomainV2::PositiveInteger)
572 );
573 for (public, core) in [
574 (UnrealSampleRateV2::Default30, CoreSampleRateV2::Default30),
575 (
576 UnrealSampleRateV2::SourceDetermined,
577 CoreSampleRateV2::SourceDetermined,
578 ),
579 (
580 UnrealSampleRateV2::CustomHz(48_000),
581 CoreSampleRateV2::CustomHz(48_000),
582 ),
583 ] {
584 assert!(SettingValueV2::SampleRate(public).matches_domain(SettingDomainV2::SampleRate));
585 assert_eq!(
586 setting_value_v2(&SettingValueV2::SampleRate(public)),
587 CoreSettingValueV2::SampleRate(core)
588 );
589 }
590 for invalid in [0, 48_001] {
591 assert!(
592 !SettingValueV2::SampleRate(UnrealSampleRateV2::CustomHz(invalid))
593 .matches_domain(SettingDomainV2::SampleRate)
594 );
595 }
596 }
597
598 #[test]
599 fn facts_encoding_sorts_every_repeated_stable_id() {
600 for original in crate::profiles_v1() {
601 let expected = facts_identity(original);
602 let mut reordered = original.clone();
603 let accepted = reordered
604 .facts
605 .iter_mut()
606 .find(|fact| fact.id() == FactId::AcceptedInputs)
607 .unwrap();
608 if let FactState::Known(FactValue::AcceptedFormats(formats)) = accepted.state() {
609 let mut formats = formats.clone();
610 formats.reverse();
611 *accepted = ProfileFact::new(
612 FactId::AcceptedInputs,
613 FactState::Known(FactValue::AcceptedFormats(formats)),
614 );
615 }
616 reordered.facts.reverse();
617 reordered.settings.reverse();
618 reordered.sources.reverse();
619 for source in &mut reordered.sources {
620 source.reverse_supported_ids_for_test();
621 }
622 assert_eq!(
623 facts_identity(&reordered),
624 expected,
625 "{:?}",
626 original.selection()
627 );
628 }
629 }
630
631 #[test]
632 fn facts_encoding_is_sensitive_to_each_record_domain() {
633 let original = crate::profiles_v1()
634 .iter()
635 .find(|profile| profile.selection.family() == "unity-generic")
636 .unwrap()
637 .clone();
638 let expected = facts_identity(&original);
639 let changed = |profile: &EngineProfile| {
640 if let Ok(projected) = project_profile(profile) {
641 assert_ne!(projected.facts_identity(), &expected);
642 }
643 };
644
645 let mut profile = original.clone();
646 profile.selection = crate::ProfileSelection::new(
647 "unity-generic-changed",
648 1,
649 "6000.3",
650 "fbx-model-importer",
651 );
652 changed(&profile);
653
654 let mut profile = original.clone();
655 profile.selection =
656 crate::ProfileSelection::new("unity-generic", 2, "6000.3", "fbx-model-importer");
657 changed(&profile);
658
659 let mut profile = original.clone();
660 profile.selection =
661 crate::ProfileSelection::new("unity-generic", 1, "6000.4", "fbx-model-importer");
662 changed(&profile);
663
664 let mut profile = original.clone();
665 profile.selection =
666 crate::ProfileSelection::new("unity-generic", 1, "6000.3", "other-importer");
667 changed(&profile);
668
669 let mut profile = original.clone();
670 profile.fact_bundle_urn = "urn:animsmith:engine-profile:unity-generic:changed";
671 changed(&profile);
672
673 let mut profile = original.clone();
674 let fact = profile
675 .facts
676 .iter_mut()
677 .find(|fact| fact.id() == FactId::ExactAxisConversion)
678 .unwrap();
679 *fact = ProfileFact::new(FactId::ExactAxisConversion, FactState::NotApplicable);
680 changed(&profile);
681
682 let mut profile = original.clone();
683 let fact = profile
684 .facts
685 .iter_mut()
686 .find(|fact| fact.id() == FactId::AcceptedInputs)
687 .unwrap();
688 *fact = ProfileFact::new(
689 FactId::AcceptedInputs,
690 FactState::Known(FactValue::AcceptedFormats(vec![
691 SourceFormatV1::Fbx,
692 SourceFormatV1::Glb,
693 ])),
694 );
695 changed(&profile);
696
697 let mut profile = original.clone();
698 let descriptor = profile.settings[0].clone();
699 profile.settings[0] = SettingDescriptor::new(
700 descriptor.id(),
701 descriptor.scope(),
702 crate::SettingDomain::BakeOrExtract,
703 descriptor.applicability(),
704 descriptor.default_status(),
705 );
706 changed(&profile);
707
708 let mut profile = original.clone();
709 let descriptor = profile.settings[0].clone();
710 profile.settings[0] = SettingDescriptor::new(
711 descriptor.id(),
712 crate::SettingScope::Clip,
713 descriptor.domain(),
714 descriptor.applicability(),
715 descriptor.default_status(),
716 );
717 changed(&profile);
718
719 let mut profile = original.clone();
720 let descriptor = profile.settings[0].clone();
721 profile.settings[0] = SettingDescriptor::new(
722 descriptor.id(),
723 descriptor.scope(),
724 descriptor.domain(),
725 crate::SettingApplicability::NotApplicable,
726 descriptor.default_status(),
727 );
728 changed(&profile);
729
730 let mut profile = original.clone();
731 let descriptor = profile.settings[0].clone();
732 profile.settings[0] = SettingDescriptor::new(
733 descriptor.id(),
734 descriptor.scope(),
735 descriptor.domain(),
736 descriptor.applicability(),
737 crate::DefaultStatus::NotApplicable,
738 );
739 changed(&profile);
740
741 let mut profile = original.clone();
742 let source = profile.sources[0].clone();
743 profile.sources[0] = crate::PrimarySource::new(
744 "changed-source-id",
745 source.target_version(),
746 source.url(),
747 source.verified_on(),
748 source.supported_facts().to_vec(),
749 source.supported_settings().to_vec(),
750 );
751 changed(&profile);
752
753 let replace_source = |profile: &mut EngineProfile,
754 target_version,
755 url,
756 verified_on,
757 facts: Vec<_>,
758 settings: Vec<_>| {
759 let source = profile.sources[0].clone();
760 profile.sources[0] = crate::PrimarySource::new(
761 source.id(),
762 target_version,
763 url,
764 verified_on,
765 facts,
766 settings,
767 );
768 };
769
770 let mut profile = original.clone();
771 let source = profile.sources[0].clone();
772 replace_source(
773 &mut profile,
774 "changed-version",
775 source.url(),
776 source.verified_on(),
777 source.supported_facts().to_vec(),
778 source.supported_settings().to_vec(),
779 );
780 changed(&profile);
781
782 let mut profile = original.clone();
783 let source = profile.sources[0].clone();
784 replace_source(
785 &mut profile,
786 source.target_version(),
787 "https://example.invalid/changed",
788 source.verified_on(),
789 source.supported_facts().to_vec(),
790 source.supported_settings().to_vec(),
791 );
792 changed(&profile);
793
794 let mut profile = original.clone();
795 let source = profile.sources[0].clone();
796 replace_source(
797 &mut profile,
798 source.target_version(),
799 source.url(),
800 "2099-01-01",
801 source.supported_facts().to_vec(),
802 source.supported_settings().to_vec(),
803 );
804 changed(&profile);
805
806 let mut profile = original.clone();
807 let source = profile.sources[0].clone();
808 let mut facts = source.supported_facts().to_vec();
809 facts.push(FactId::AcceptedInputs);
810 replace_source(
811 &mut profile,
812 source.target_version(),
813 source.url(),
814 source.verified_on(),
815 facts,
816 source.supported_settings().to_vec(),
817 );
818 changed(&profile);
819
820 let mut profile = original.clone();
821 let source = profile.sources[0].clone();
822 let mut settings = source.supported_settings().to_vec();
823 settings.pop();
824 replace_source(
825 &mut profile,
826 source.target_version(),
827 source.url(),
828 source.verified_on(),
829 source.supported_facts().to_vec(),
830 settings,
831 );
832 changed(&profile);
833
834 let unreal = crate::profiles_v1()
835 .iter()
836 .find(|profile| profile.selection.family() == "unreal")
837 .unwrap();
838 let expected = facts_identity(unreal);
839 let mut profile = unreal.clone();
840 let fact = profile
841 .facts
842 .iter_mut()
843 .find(|fact| fact.id() == FactId::TargetCoordinateBasis)
844 .unwrap();
845 *fact = ProfileFact::new(
846 FactId::TargetCoordinateBasis,
847 FactState::Known(FactValue::CoordinateBasis(crate::CoordinateBasis {
848 handedness: crate::Handedness::Left,
849 up_axis: crate::UpAxis::Z,
850 forward_axis: crate::ForwardAxis::NegativeX,
851 })),
852 );
853 assert_ne!(facts_identity(&profile), expected);
854 }
855}