use std::collections::{BTreeMap, HashMap, HashSet};
use std::path::PathBuf;
use openusd::{sdf, tf, usd};
use crate::plug_info::Plugin;
pub use crate::load::DeclaredToken;
pub const TYPED: &str = "Typed";
pub const API_SCHEMA_BASE: &str = "APISchemaBase";
pub const SCHEMA_BASE: &str = "SchemaBase";
pub const API_SCHEMA_OVERRIDE: &str = "apiSchemaOverride";
pub const NON_APPLIED: &str = "nonApplied";
#[derive(Debug)]
pub struct Library {
pub name: String,
pub use_literal_identifiers: bool,
pub skip_code_generation: bool,
pub classes: Vec<Class>,
pub reflected: Vec<Class>,
pub inherited: Vec<Class>,
pub foreign: Vec<ForeignClass>,
pub source_layers: Vec<PathBuf>,
pub declared_tokens: Vec<DeclaredToken>,
pub metadata: Vec<MetadataField>,
pub kinds: Vec<KindDecl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KindDecl {
pub name: String,
pub base: Option<String>,
}
#[derive(Debug, Clone)]
pub struct MetadataField {
pub name: tf::Token,
pub type_name: sdf::ValueTypeName,
pub applies_to: usd::MetadataTargets,
pub fallback: Option<sdf::Value>,
pub documentation: Option<String>,
}
#[derive(Debug)]
pub struct Class {
pub identifier: tf::Token,
pub family: tf::Token,
pub version: u32,
pub library: String,
pub upstream_name: String,
pub kind: usd::SchemaKind,
pub bases: Vec<Base>,
pub properties: Vec<Property>,
pub applied_api_schemas: Vec<tf::Token>,
pub documentation: Option<String>,
pub is_typed: bool,
pub authored_base_count: usize,
pub direct_base: Option<tf::Token>,
pub authored_fields: Vec<String>,
pub fields: BTreeMap<String, sdf::Value>,
pub authored_type_name: Option<tf::Token>,
pub api_schemas_op: Option<sdf::TokenListOp>,
pub metadata: Metadata,
pub origin: Origin,
}
#[derive(Debug, Clone)]
pub struct Base {
pub identifier: tf::Token,
pub library: String,
pub generated: bool,
pub reflected_api_schemas: Vec<tf::Token>,
pub class_name: String,
pub kind: usd::SchemaKind,
}
#[derive(Debug)]
pub struct ForeignClass {
pub library: String,
pub class_name: String,
pub upstream_name: String,
}
#[derive(Debug)]
pub enum Reflection<'a> {
Taken {
schema: &'a Class,
properties: Vec<&'a Property>,
shadowed: Vec<(&'a Property, &'a tf::Token)>,
},
NotSingleApply(&'a Class),
NotApplied(&'a Class),
}
#[derive(Debug, Default)]
pub struct Metadata {
pub property_namespace_prefix: Option<tf::Token>,
pub auto_apply_to: Vec<tf::Token>,
pub can_only_apply_to: Vec<tf::Token>,
pub allowed_instance_names: Vec<tf::Token>,
pub instance_restrictions: BTreeMap<tf::Token, Vec<tf::Token>>,
pub fallback_types: Vec<tf::Token>,
pub class_name: String,
pub reflected_api_schemas: Vec<tf::Token>,
pub schema_tokens: Vec<DeclaredToken>,
}
#[derive(Debug)]
pub struct Property {
pub name: tf::Token,
pub schematics_name: tf::Token,
pub shape: Shape,
pub sites: Vec<Site>,
pub is_local: bool,
pub api: PropertyApi,
pub fields: BTreeMap<String, sdf::Value>,
pub origin: Origin,
}
#[derive(Debug)]
pub struct Site {
pub class: tf::Token,
pub is_override: bool,
pub shape: Shape,
pub api_name: Option<String>,
pub origin: Origin,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shape {
pub spec_type: sdf::SpecType,
pub type_name: Option<tf::Token>,
pub variability: sdf::Variability,
pub custom: bool,
}
#[derive(Debug, Default)]
pub struct PropertyApi {
pub custom_get: bool,
}
#[derive(Debug, Clone)]
pub struct Origin {
pub layer: String,
pub path: sdf::Path,
}
impl Library {
pub fn declarations(name: &str) -> Self {
Library {
name: name.to_owned(),
use_literal_identifiers: true,
skip_code_generation: false,
classes: Vec::new(),
reflected: Vec::new(),
inherited: Vec::new(),
foreign: Vec::new(),
source_layers: Vec::new(),
declared_tokens: Vec::new(),
metadata: Vec::new(),
kinds: Vec::new(),
}
}
pub fn take_declarations(&mut self, plugins: &[Plugin]) {
for plugin in plugins.iter().filter(|plugin| plugin.name == self.name) {
self.metadata.extend(plugin.fields.iter().cloned());
self.kinds.extend(plugin.kinds.iter().cloned());
self.source_layers.push(plugin.path.clone());
}
}
pub fn emitted(&self) -> impl Iterator<Item = &Class> {
self.classes.iter().chain(&self.reflected)
}
pub fn held(&self) -> impl Iterator<Item = &Class> {
self.emitted().chain(&self.inherited)
}
pub fn find(&self, identifier: &tf::Token) -> Option<&Class> {
self.held().find(|class| &class.identifier == identifier)
}
}
impl Class {
pub fn local_properties(&self) -> impl Iterator<Item = &Property> {
self.properties.iter().filter(|property| property.is_local)
}
pub fn reflections<'a>(&self, library: &'a Library) -> Vec<Reflection<'a>> {
let inherited: HashSet<&tf::Token> = self.bases.iter().flat_map(|base| &base.reflected_api_schemas).collect();
let mut named: HashSet<&tf::Token> = HashSet::new();
let mut taken: HashMap<&'a tf::Token, &'a tf::Token> = HashMap::new();
self.metadata
.reflected_api_schemas
.iter()
.filter(|name| named.insert(*name) && !inherited.contains(*name))
.filter_map(|name| library.find(name))
.map(|schema| {
if schema.kind != usd::SchemaKind::SingleApplyApi {
return Reflection::NotSingleApply(schema);
}
if !self.applied_api_schemas.contains(&schema.identifier) {
return Reflection::NotApplied(schema);
}
let mut properties = Vec::new();
let mut shadowed = Vec::new();
for property in schema.local_properties() {
if let Some(first) = taken.get(&property.name) {
shadowed.push((property, *first));
} else {
taken.insert(&property.name, &schema.identifier);
properties.push(property);
}
}
Reflection::Taken {
schema,
properties,
shadowed,
}
})
.collect()
}
pub fn override_properties(&self) -> impl Iterator<Item = &Property> {
self.properties.iter().filter(|property| property.is_override())
}
}
impl Property {
pub fn type_name(&self) -> Option<sdf::ValueTypeName> {
sdf::ValueTypeName::find(self.shape.type_name.as_ref()?)
}
pub fn fallback(&self) -> Option<&sdf::Value> {
self.fields.get(sdf::FieldKey::Default.as_str())
}
pub fn allowed_tokens(&self) -> Vec<tf::Token> {
allowed_tokens(self.fields.get(sdf::FieldKey::AllowedTokens.as_str()))
}
pub fn documentation(&self) -> Option<&str> {
self.fields
.get(sdf::FieldKey::Documentation.as_str())?
.try_as_string_ref()
.map(String::as_str)
}
pub fn api_name(&self) -> Option<&str> {
let site = self.sites.first()?;
self.is_local.then_some(site.api_name.as_deref()).flatten()
}
pub fn has_accessor(&self) -> bool {
self.api_name().is_some()
}
pub fn is_override(&self) -> bool {
self.sites.first().is_some_and(|site| site.is_override)
}
}
impl Origin {
pub fn describe(&self) -> String {
format!("{}{}", self.layer, self.path)
}
}
pub fn is_root(name: &tf::Token) -> bool {
matches!(name.as_str(), TYPED | API_SCHEMA_BASE | SCHEMA_BASE) || {
let (family, _) = usd::SchemaRegistry::parse_schema_family_and_version(name);
matches!(family.as_str(), TYPED | API_SCHEMA_BASE | SCHEMA_BASE)
}
}
pub fn allowed_tokens(value: Option<&sdf::Value>) -> Vec<tf::Token> {
match value {
Some(sdf::Value::TokenVec(tokens)) => tokens.clone(),
Some(sdf::Value::StringVec(texts)) => texts.iter().map(|text| tf::Token::from(text.as_str())).collect(),
_ => Vec::new(),
}
}