Skip to content
34 changes: 34 additions & 0 deletions crates/openusd/src/ar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ pub trait Asset: Read + Seek + Send {
/// Returns the total size of the asset in bytes.
fn size(&self) -> io::Result<u64>;

/// The complete asset as bytes shared with the caller, whatever the
/// cursor position, for an asset already held in memory (C++
/// `ArAsset::GetBuffer`). A format that decodes in place keeps them
/// instead of copying; `None`, the default, has the asset read instead.
Comment on lines +121 to +124
fn shared_bytes(&self) -> Option<std::sync::Arc<[u8]>> {
None
}

/// Reads the entire asset into a byte buffer.
fn read_all(&mut self) -> io::Result<Vec<u8>> {
let size = self.size()? as usize;
Expand All @@ -137,6 +145,17 @@ impl Asset for io::Cursor<Vec<u8>> {
fn size(&self) -> io::Result<u64> {
Ok(self.get_ref().len() as u64)
}

/// A cursor still at its start hands its buffer over instead of copying
/// it, leaving itself empty.
fn read_all(&mut self) -> io::Result<Vec<u8>> {
if self.position() != 0 {
let mut buf = Vec::new();
self.read_to_end(&mut buf)?;
return Ok(buf);
}
Ok(std::mem::take(self.get_mut()))
}
}

/// Interface for resolving asset paths to physical locations.
Expand Down Expand Up @@ -1057,6 +1076,21 @@ mod tests {
assert_eq!(result, data);
}

/// A cursor at its start hands its buffer over; one already read into
/// returns the rest.
#[test]
fn cursor_asset_read_all_moves_buffer() {
let data = b"hello world".to_vec();
let pointer = data.as_ptr();
let mut asset = io::Cursor::new(data);
let moved = asset.read_all().unwrap();
assert_eq!(moved.as_ptr(), pointer);

let mut asset = io::Cursor::new(b"hello world".to_vec());
asset.seek(io::SeekFrom::Start(6)).unwrap();
assert_eq!(asset.read_all().unwrap(), b"world");
}

#[test]
fn cursor_asset_seek() {
let data = b"hello world".to_vec();
Expand Down
75 changes: 75 additions & 0 deletions crates/openusd/src/pcp/index_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2090,6 +2090,81 @@ impl IndexCache {
Ok(true)
}

/// Whether `path` or any ancestor below the pseudo-root resolves to
/// `class` (C++ `UsdPrim::IsAbstract`). A prim with no composed spec is
/// not abstract, and a prototype root is `def` whatever its source says,
/// as C++ `Usd_PrimData` sets it. Resolved like [`Self::is_defined`],
/// under one cache borrow for the whole ancestor chain.
pub(crate) fn is_abstract(&mut self, graph: &LayerGraph, path: &Path) -> Result<bool, QueryError> {
if path.is_abs_root() || !self.has_spec(graph, path)? {
return Ok(false);
}
for ancestor in path.ancestors_below_root() {
if self.is_prototype(&ancestor) {
break;
}
let specifier = self
.resolve_field(graph, &ancestor, FieldKey::Specifier.as_str())?
.map(sdf::Specifier::try_from)
.transpose()?;
if specifier == Some(sdf::Specifier::Class) {
return Ok(true);
}
}
Ok(false)
}

/// [`Self::is_defined`] and [`Self::is_abstract`] together, from one walk
/// up the ancestor chain, for a caller asking both.
pub(crate) fn specifier_status(&mut self, graph: &LayerGraph, path: &Path) -> Result<(bool, bool), QueryError> {
if path.is_abs_root() {
return Ok((true, false));
}
if !self.has_spec(graph, path)? {
return Ok((false, false));
}
let (mut defined, mut is_abstract) = (true, false);
for ancestor in path.ancestors_below_root() {
if self.is_prototype(&ancestor) {
break;
}
let specifier = self
.resolve_field(graph, &ancestor, FieldKey::Specifier.as_str())?
.map(sdf::Specifier::try_from)
.transpose()?;
defined &= matches!(specifier, Some(sdf::Specifier::Def | sdf::Specifier::Class));
is_abstract |= specifier == Some(sdf::Specifier::Class);
if !defined && is_abstract {
break;
}
}
Ok((defined, is_abstract))
}

/// The active, defined and abstract state of `path` from its own opinions
/// alone, for a prim whose parent resolved active, defined and not
/// abstract: below such a parent, [`Self::is_active`],
/// [`Self::is_defined`] and [`Self::is_abstract`] are each decided at the
/// prim itself.
pub(crate) fn local_status(&mut self, graph: &LayerGraph, path: &Path) -> Result<(bool, bool, bool), QueryError> {
if !self.has_spec(graph, path)? {
return Ok((false, false, false));
}
let active = self.active_locally(graph, path)?;
if self.is_prototype(path) {
return Ok((active, true, false));
}
let specifier = self
.resolve_field(graph, path, FieldKey::Specifier.as_str())?
.map(sdf::Specifier::try_from)
.transpose()?;
Ok((
active,
matches!(specifier, Some(sdf::Specifier::Def | sdf::Specifier::Class)),
specifier == Some(sdf::Specifier::Class),
))
}

/// This prim's own composed `active` opinion, defaulting to `true`. The
/// per-prim read [`Self::is_active`] walks and [`Self::is_populated`] takes
/// for the prim it is deciding, its ancestors having been decided already.
Expand Down
13 changes: 12 additions & 1 deletion crates/openusd/src/sdf/file_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,25 @@ pub trait FileFormat: Sync {
/// decode without a copy while bytes just read off disk move in.
fn read_bytes(&self, bytes: Cow<'static, [u8]>, source_name: &str) -> Result<LayerData, FormatError>;

/// [`read_bytes`](Self::read_bytes) for bytes shared with an asset
/// ([`ar::Asset::shared_bytes`]). A format that decodes in place keeps
/// them; the default copies them into `read_bytes`.
fn read_shared_bytes(&self, bytes: std::sync::Arc<[u8]>, source_name: &str) -> Result<LayerData, FormatError> {
self.read_bytes(bytes.as_ref().to_vec().into(), source_name)
}

/// Read a layer's data from `resolved`, opening the asset (and any
/// sibling assets) through `resolver`.
///
/// A format that reads nothing but its own bytes needs only
/// [`read_bytes`](Self::read_bytes); one that reaches for sibling assets
/// overrides this.
fn read(&self, resolver: &dyn ar::Resolver, resolved: &ar::ResolvedPath) -> Result<LayerData, FormatError> {
let bytes = resolver.open_asset(resolved)?.read_all()?;
let mut asset = resolver.open_asset(resolved)?;
if let Some(bytes) = asset.shared_bytes() {
return self.read_shared_bytes(bytes, &resolved.to_string());
}
let bytes = asset.read_all()?;
self.read_bytes(bytes.into(), &resolved.to_string())
}

Expand Down
99 changes: 77 additions & 22 deletions crates/openusd/src/sdf/layer_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ pub struct LayerRegistry {
resolver: Box<dyn ar::Resolver>,
}

/// A stack's root layer, read ahead of its stack by
/// [`LayerRegistry::prepare_root`] and handed to
/// [`LayerRegistry::open_prepared_stack`] so it is not read twice.
Comment on lines +150 to +152
pub(crate) struct PreparedLayer {
/// The layer's canonical identifier.
pub identifier: String,
/// Where the layer was found.
pub resolved: ar::ResolvedPath,
/// The layer's data.
pub data: sdf::LayerData,
}

impl Default for LayerRegistry {
/// A registry over the filesystem [`DefaultResolver`](ar::DefaultResolver)
/// and the built-in formats — what [`Stage::builder`](crate::usd::Stage)
Expand Down Expand Up @@ -256,6 +268,20 @@ impl LayerRegistry {
.read_bytes(bytes, source_name)
}

/// [`read_bytes`](Self::read_bytes) for bytes shared with an asset, which
/// a format that decodes in place keeps rather than copies.
pub fn read_shared_bytes(
bytes: std::sync::Arc<[u8]>,
source_name: &str,
) -> Result<sdf::LayerData, sdf::FormatError> {
DEFAULT_FORMATS
.iter()
.copied()
.find(|format| format.matches_content(&bytes))
.ok_or_else(|| sdf::FormatError::Unrecognized(source_name.into()))?
.read_shared_bytes(bytes, source_name)
}

/// Find the format claiming `ext` (without the leading dot, case-insensitive),
/// e.g. `"usda"` or `"usd"`. C++ `SdfFileFormat::FindByExtension`.
pub fn find_by_extension(ext: &str) -> Option<&'static dyn sdf::FileFormat> {
Expand Down Expand Up @@ -292,30 +318,32 @@ impl LayerRegistry {
}
}

/// The `expressionVariables` authored on the single layer at `asset_path`
/// (anchored against `anchor`), read without opening its sublayers — the shallow
/// read the stage root stack needs to compose its root and session layers' own
/// variables into one context before either region's sublayer subtree is
/// collected. An empty identifier yields an empty map; a resolve or read failure
/// propagates.
///
/// TODO(perf): the layer read here is read again when its stack is collected;
/// the registry does not cache reads, so a root or session layer is parsed twice
/// at open.
pub(crate) fn own_expression_variables(
/// Reads the single layer at `asset_path` (anchored against `anchor`)
/// without opening its sublayers: the shallow read the stage root stack
/// needs to compose its root and session layers' own expression variables
/// into one context before either region's sublayer subtree is collected.
/// The read is then handed to
/// [`open_prepared_stack`](Self::open_prepared_stack), so the layer is
/// parsed once at open. An empty identifier yields `None`; a resolve or
/// read failure propagates.
pub(crate) fn prepare_root(
&self,
asset_path: &str,
anchor: Option<&ar::ResolvedPath>,
) -> Result<HashMap<String, sdf::Value>, LoadError> {
) -> Result<Option<PreparedLayer>, LoadError> {
let identifier = self.create_identifier(asset_path, anchor);
if identifier.is_empty() {
return Ok(HashMap::new());
return Ok(None);
}
let resolved = self.resolve_layer(&identifier).ok_or_else(|| LoadError::Unresolved {
asset_path: asset_path.to_owned(),
})?;
let data = self.read(&resolved)?;
Ok(expr::read_expression_variables(data.as_ref())?.into_owned())
Ok(Some(PreparedLayer {
identifier,
resolved,
data,
}))
}

/// Opens the layer at `identifier` — a canonical identifier, as
Expand Down Expand Up @@ -363,9 +391,6 @@ impl LayerRegistry {
on_error: &dyn Fn(Error) -> Result<(), Error>,
already_present: &dyn Fn(&str) -> bool,
) -> Result<Option<Vec<sdf::Layer>>, LoadError> {
let mut layers = Vec::new();
let mut visited = HashSet::new();

if identifier.is_empty() {
return Ok(None);
}
Expand All @@ -379,6 +404,36 @@ impl LayerRegistry {
return Ok(None);
};
let data = self.read(&resolved)?;
self.open_prepared_stack(
PreparedLayer {
identifier,
resolved,
data,
},
ancestor_expr_vars,
reload,
on_error,
already_present,
)
}

/// [`open_stack`](Self::open_stack) from a root layer already read by
/// [`prepare_root`](Self::prepare_root).
pub(crate) fn open_prepared_stack(
&self,
root: PreparedLayer,
ancestor_expr_vars: &HashMap<String, sdf::Value>,
reload: bool,
on_error: &dyn Fn(Error) -> Result<(), Error>,
already_present: &dyn Fn(&str) -> bool,
) -> Result<Option<Vec<sdf::Layer>>, LoadError> {
let PreparedLayer {
identifier,
resolved,
data,
} = root;
let mut layers = Vec::new();
let mut visited = HashSet::new();
visited.insert(identifier.clone());

// The whole stack resolves its `${VAR}` sublayers against one context (C++
Expand Down Expand Up @@ -454,11 +509,11 @@ impl LayerRegistry {
fn read(&self, resolved: &ar::ResolvedPath) -> Result<sdf::LayerData, LoadError> {
let ext = resolved.extension();
if ext.eq_ignore_ascii_case("usd") {
let bytes = self
.resolver
.open_asset(resolved)
.and_then(|mut asset| asset.read_all())
.map_err(sdf::FormatError::from)?;
let mut asset = self.resolver.open_asset(resolved).map_err(sdf::FormatError::from)?;
if let Some(bytes) = asset.shared_bytes() {
return Ok(Self::read_shared_bytes(bytes, &resolved.to_string())?);
}
let bytes = asset.read_all().map_err(sdf::FormatError::from)?;
return Ok(Self::read_bytes(bytes.into(), &resolved.to_string())?);
}
Ok(Self::find_by_extension(&ext)
Expand Down
Loading
Loading