Skip to main content

openusd/sdf/
layer.rs

1//! A single scene-description layer (the Rust equivalent of C++ `SdfLayer`):
2//! identity plus a backing [`AbstractData`] under a copy-on-write [`CowData`]
3//! staging overlay.
4//!
5//! # Editing
6//!
7//! A layer is mutated only through [`Layer::edit`] (one layer) or [`edit_layers`]
8//! (several at once, atomically): the closure authors through a [`LayerEdit`]
9//! view — [`PrimSpec::new`](super::PrimSpec::new) and friends write through
10//! [`LayerEdit::data_mut`] — and the whole batch commits when the closure returns
11//! `Ok`, or rolls back on its error or a panic. Committing drains the overlay into
12//! the backend and yields the derived [`ChangeList`] for composition
13//! invalidation. [`dry_run_layers`] runs the same staging but always discards it,
14//! to check an edit applies without keeping it. Reads merge staged-over-base, so
15//! an in-progress edit is visible to reads inside its closure.
16//!
17//! # Why copy-on-write
18//!
19//! Staging is the primitive that makes three things the editing pipeline relies on
20//! cheap and correct (the alternative is to edit the backend immediately and track
21//! changes as they happen):
22//!
23//! - Net-effect change records. A composition invalidation should reflect the
24//!   final state of an edit, not the churn along the way. Because the overlay holds
25//!   the net staged state, the [`ChangeList`] derived from it at commit reflects
26//!   only that final result: a field set twice records once, and a spec created
27//!   then deleted within one edit records nothing — never the in-between steps an
28//!   immediate-edit log would carry.
29//! - Old and new values side by side. At commit the pristine base and the staged
30//!   overlay both exist, so a forward log (the new values) and an inverse log (the
31//!   old values, for undo) fall out directly from the diff — no need to snapshot
32//!   the old value at each mutation before it is overwritten.
33//! - All-or-nothing edits. A multi-step edit — or a multi-layer one, where a
34//!   namespace edit must apply across several layers together — either commits
35//!   whole or rolls back whole. Rolling back is just dropping the overlay, so
36//!   abandoning a half-built or vetoed edit costs nothing and leaves no partial
37//!   state behind.
38//!
39//! Loading a layer and its sublayer stack lives in
40//! [`LayerRegistry`](super::LayerRegistry); cross-layer composition (references,
41//! payloads) lives in [`crate::pcp`].
42
43use std::collections::HashMap;
44use std::fmt;
45use std::io::{self, Cursor, Write};
46use std::path::Path as FsPath;
47use std::rc::Rc;
48
49use crate::{ar, sdf, tf};
50
51use super::schema::FieldKey;
52use super::{
53    AbstractData, AttributeSpecMut, AttributeSpecRef, ChangeList, CowData, Data, DataError, FormatError, IntoPath,
54    LayerData, Patch, Path, PathParseError, PrimSpecMut, PrimSpecRef, PseudoRootSpecMut, PseudoRootSpecRef,
55    RelationshipSpecMut, RelationshipSpecRef, RelocateList, SpecError, SpecType, Value, ValueTypeError, sink,
56    try_into_path,
57};
58
59/// A [`sink::Id`] for a [`LayerSink`] installed on a [`Layer`].
60pub type LayerSinkId = sink::Id<dyn LayerSink>;
61
62/// Prefix marking an anonymous layer identifier (`anon:<n>:<tag>`), the single
63/// source of truth shared by minting and [`Layer::is_anonymous`].
64const ANONYMOUS_PREFIX: &str = "anon:";
65
66/// A single loaded layer in the composition.
67pub struct Layer {
68    /// Resolved, canonical identifier for this layer.
69    pub identifier: String,
70    /// The layer's resolved physical location when it differs from
71    /// [`identifier`](Self::identifier) — the anchor for the relative asset
72    /// paths it authors (C++ `SdfLayer::GetRealPath`). `None` (the common case)
73    /// means it equals the identifier; a package (`.usdz`) opens under the bare
74    /// package identifier while its real path is the package-relative default
75    /// layer (`pkg.usdz[root.usd]`), so paths authored inside it anchor
76    /// in-package against the real path rather than the identifier, and a layer
77    /// found through a resolver search directory keeps the search path as its
78    /// identifier while its real path is the file that directory holds. Read
79    /// through [`real_path`](Self::real_path), which falls back to the
80    /// identifier.
81    real_path: Option<String>,
82    /// The parsed scene description data, under a copy-on-write [`CowData`]
83    /// staging overlay. Every authoring write stages in the overlay; committing an
84    /// edit derives the composition record and drains the overlay into the backend.
85    /// Private so external callers reach it only through [`data`](Self::data) for
86    /// reads or a [`LayerEdit`] for authoring, keeping the authoring API's
87    /// bookkeeping invariants (`primChildren`, `propertyChildren`, ancestor
88    /// specifiers, …) in force.
89    data: CowData<LayerData>,
90    /// The change record for the edit in progress, derived from the overlay at
91    /// commit and handed to the layer's sinks. Owned and refilled in place each
92    /// commit (reusing its buffer) rather than reallocated; empty between edits.
93    changes: ChangeList,
94    /// Per-layer change sinks fired at the commit seam — the low tier of the
95    /// change pipeline, where a stage installs an aggregator so it stays in sync
96    /// with any edit. Empty by default.
97    sinks: sink::Set<dyn LayerSink>,
98    /// The resolver the layer was opened through, which a save reopens the
99    /// saved file with. `None` for a layer that was not read from an asset.
100    resolver: Option<Rc<dyn ar::Resolver>>,
101}
102
103impl Layer {
104    /// Construct a layer from a resolved identifier and a backing data store.
105    /// `identifier` is used verbatim, so it must already be the canonical
106    /// spelling under the resolver of the graph the layer joins: the one
107    /// [`in_memory_identifier`](ar::Resolver::in_memory_identifier) returns
108    /// for it. Crate-private — external callers should use
109    /// [`Layer::new_anonymous`] for blank in-memory layers,
110    /// [`open`](Self::open) for one on disk, or
111    /// [`from_bytes`](Self::from_bytes) for one already in hand.
112    pub(crate) fn new(identifier: impl Into<String>, data: LayerData) -> Self {
113        Self::build(identifier.into(), None, data)
114    }
115
116    /// Open the layer at `identifier` off the filesystem, read in whichever
117    /// format claims it.
118    ///
119    /// The layer is read alone: the sublayers, references and payloads it
120    /// declares stay the asset paths it authored. Composing them is
121    /// [`Stage::open`](crate::usd::Stage::open)'s job.
122    pub fn open(identifier: impl AsRef<str>) -> crate::Result<Self> {
123        Self::open_with(ar::DefaultResolver::new(), identifier)
124    }
125
126    /// Open the layer at `identifier`, resolving and reading it through
127    /// `resolver` — the door for layers a host serves from somewhere other than
128    /// the filesystem. See [`open`](Self::open).
129    pub fn open_with(resolver: impl ar::Resolver + 'static, identifier: impl AsRef<str>) -> crate::Result<Self> {
130        let registry = sdf::LayerRegistry::new(Box::new(resolver));
131        // Canonicalized first, as every other load path does: the layer is
132        // addressed by the identifier it keeps, so a caller's spelling of one
133        // file must not become a second identity for it.
134        let identifier = registry.create_identifier(identifier.as_ref(), None);
135        let layer = registry.open_layer(&identifier)?;
136        layer.ok_or(crate::Error::UnresolvedAsset(identifier))
137    }
138
139    /// Decode `bytes` as an anonymous layer tagged `tag`, in whichever format
140    /// their content names (see
141    /// [`LayerRegistry::read_bytes`](sdf::LayerRegistry::read_bytes)).
142    ///
143    /// For layer bytes that were never an asset — compiled into a program, or
144    /// handed over by a host. The layer is anonymous because it resolved from
145    /// nowhere, so the relative asset paths it authors anchor against nothing;
146    /// C++ opens the schema data it compiles against the same way
147    /// (`SdfLayer::OpenAsAnonymous`). `tag` names their origin for diagnostics,
148    /// as in [`new_anonymous`](Self::new_anonymous).
149    pub fn from_bytes(tag: impl fmt::Display, bytes: impl Into<ar::AssetBuffer>) -> Result<Self, FormatError> {
150        let identifier = Self::anonymous_identifier(tag);
151        let data = sdf::LayerRegistry::read_bytes(bytes.into(), &identifier)?;
152        Ok(Self::new(identifier, data))
153    }
154
155    /// Construct a loaded layer recording its resolved physical location.
156    ///
157    /// The loader passes the [`real_path`](Self::real_path) it resolved the
158    /// layer to: it equals `identifier` for an ordinary layer, but a package
159    /// opens under its bare identifier while its real path is the
160    /// package-relative default layer, so the paths it authors anchor
161    /// in-package. Only a real path that differs from the identifier is
162    /// stored. The layer keeps `resolver`, the one it was read through, for
163    /// [`save`](Self::save) to reopen its file with.
164    pub(crate) fn new_resolved(
165        identifier: impl Into<String>,
166        real_path: &ar::ResolvedPath,
167        data: LayerData,
168        resolver: Rc<dyn ar::Resolver>,
169    ) -> Self {
170        let identifier = identifier.into();
171        let real_path = real_path.to_string();
172        let real_path = (real_path != identifier).then_some(real_path);
173        let mut layer = Self::build(identifier, real_path, data);
174        layer.resolver = Some(resolver);
175        layer
176    }
177
178    /// Shared constructor backing [`new`](Self::new) and
179    /// [`new_resolved`](Self::new_resolved).
180    fn build(identifier: String, real_path: Option<String>, data: LayerData) -> Self {
181        Self {
182            identifier,
183            real_path,
184            data: CowData::new(data),
185            changes: ChangeList::new(),
186            sinks: sink::Set::default(),
187            resolver: None,
188        }
189    }
190
191    /// Install a [`LayerSink`] fired at this layer's commit seam, returning its
192    /// [`LayerSinkId`] for a later [`remove_sink`](Self::remove_sink). Any
193    /// editor of this layer — a stage, the namespace editor, raw authoring —
194    /// fires the sink, so an external observer sees every committed edit. A bare
195    /// `Fn(&str, &ChangeList)` closure is a `LayerSink`, so this takes either a
196    /// full sink type or a closure observer.
197    pub fn add_sink<S: LayerSink + 'static>(&mut self, sink: S) -> LayerSinkId {
198        self.sinks.add(Box::new(sink))
199    }
200
201    /// Remove the layer sink with `id`; a no-op if it was already removed.
202    pub fn remove_sink(&mut self, id: LayerSinkId) {
203        self.sinks.remove(id);
204    }
205
206    /// Borrow the layer's data as a read-only [`AbstractData`]. Reads see the
207    /// staged-over-base view, so edits staged but not yet flushed are visible.
208    pub fn data(&self) -> &dyn AbstractData {
209        &self.data
210    }
211
212    /// Discard the layer's staged (uncommitted) edits, restoring the backend to
213    /// its last-committed state. Already-committed content is unaffected. Private:
214    /// staging and committing are driven only through [`edit`](Self::edit) /
215    /// [`edit_layers`], which roll back automatically on failure, so a caller
216    /// never sequences a rollback by hand.
217    fn rollback(&mut self) {
218        self.data.rollback();
219    }
220
221    /// Author a batch of edits as one atomic unit: run `f` against the layer's
222    /// [`LayerEdit`] view, then commit on success, or roll back on `f`'s error, a
223    /// sink veto, or a panic. Returns whether the edit produced a composition
224    /// change. This is the only way to mutate a layer — the commit is the closing
225    /// brace, not a separate call a caller can omit — so an edit is never left
226    /// staged-but-uncommitted. Mirrors C++ scene description committing through
227    /// `Sdf_ChangeManager`. To edit several of a stage's layers atomically, use
228    /// [`Stage::batch_edit`](crate::usd::Stage::batch_edit).
229    ///
230    /// Panics if called re-entrantly on the same layer.
231    ///
232    /// # Example
233    ///
234    /// ```
235    /// use openusd::sdf::{self, PrimSpec, Specifier};
236    ///
237    /// let mut layer = sdf::Layer::new_anonymous("example.usda");
238    /// // On success the batch commits and the change record is returned.
239    /// layer
240    ///     .edit(|l| {
241    ///         PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
242    ///         Ok(())
243    ///     })
244    ///     .expect("authored and committed");
245    /// assert!(layer.prim("/World").unwrap().is_some());
246    ///
247    /// // If the closure returns an error, the whole batch rolls back — neither
248    /// // spec below survives, because the second authoring call fails.
249    /// let result = layer.edit(|l| {
250    ///     PrimSpec::new(l.data_mut(), "/Good", Specifier::Def, "Xform")?;
251    ///     PrimSpec::new(l.data_mut(), "/Good.prop", Specifier::Def, "Xform")?; // property path: invalid for a prim
252    ///     Ok(())
253    /// });
254    /// assert!(result.is_err());
255    /// assert!(layer.prim("/Good").unwrap().is_none());
256    /// ```
257    pub fn edit(
258        &mut self,
259        f: impl FnOnce(&mut LayerEdit<'_>) -> Result<(), AuthoringError>,
260    ) -> Result<bool, EditError> {
261        edit_layers(&mut [self], |edits| f(&mut edits[0]).map_err(EditError::from))
262    }
263
264    /// Refill this layer's change record from the staged overlay against the
265    /// still-pristine base, and offer it to each sink's
266    /// [`before_commit`](LayerSink::before_commit) while the overlay is intact,
267    /// tagged with the enclosing transaction's `generation`. A sink's rejection
268    /// leaves the overlay staged for the caller to roll back. Does not touch the
269    /// backend.
270    fn prepare_commit(&mut self, generation: u64) -> Result<(), sink::Error> {
271        self.changes.update(&self.data);
272        if !self.changes.is_empty() && !self.sinks.is_empty() {
273            let change = PendingLayerChange {
274                layer_identifier: &self.identifier,
275                base: &**self.data.base(),
276                overlay: self.data.overlay(),
277                change_list: &self.changes,
278                generation,
279            };
280            self.sinks.iter().try_for_each(|sink| sink.before_commit(&change))?;
281        }
282        Ok(())
283    }
284
285    /// Drain the overlay into the backend — the irreversible half of a commit,
286    /// run once [`prepare_commit`](Self::prepare_commit) has accepted the edit.
287    /// Returns whether the edit produced any composition change. The derived
288    /// record stays in [`changes`](Self::changes) for a following
289    /// [`notify_after_commit`](Self::notify_after_commit).
290    fn commit_data(&mut self) -> bool {
291        self.data.commit();
292        !self.changes.is_empty()
293    }
294
295    /// Fire each sink's [`after_commit`](LayerSink::after_commit) with the record
296    /// [`prepare_commit`](Self::prepare_commit) derived. Run only after every layer
297    /// in a group has committed its data, so a panicking sink cannot strand a
298    /// partially committed group.
299    fn notify_after_commit(&self) {
300        if !self.changes.is_empty() {
301            for sink in self.sinks.iter() {
302                sink.after_commit(&self.identifier, &self.changes);
303            }
304        }
305    }
306
307    /// Whether the layer has edits staged in its overlay but not yet committed.
308    /// Mirrors C++ `SdfLayer::IsDirty` for the in-flight-edit case.
309    ///
310    /// Conservative about staged content: any uncommitted overlay write counts,
311    /// including an idempotent edit (a field re-set to the value the base already
312    /// holds) whose commit would derive no composition change.
313    pub fn is_dirty(&self) -> bool {
314        !self.data.is_empty()
315    }
316
317    /// Commit any staged edits into the backend to keep their content, without
318    /// deriving a change record or notifying sinks. Called when a layer joins a
319    /// stage: its content is composed fresh by the layer-stack recompose, so a
320    /// record from prior direct edits would be redundant.
321    pub(crate) fn discard_changes(&mut self) {
322        self.data.commit();
323    }
324
325    /// The layer's resolved, canonical identifier.
326    pub fn identifier(&self) -> &str {
327        &self.identifier
328    }
329
330    /// Where the layer was found, or `None` for an anonymous layer, which was
331    /// never read from anywhere (C++ `SdfLayer::GetRealPath`, empty for one).
332    ///
333    /// This is the identifier except for a package, whose location is its
334    /// package-relative default layer, and a layer found through a resolver
335    /// search directory, whose identifier is the bare search path it was asked
336    /// for.
337    pub fn resolved_path(&self) -> Option<&str> {
338        (!self.is_anonymous()).then(|| self.real_path())
339    }
340
341    /// The layer's resolved physical location, the anchor for the relative
342    /// asset paths it authors. Falls back to the identifier, which is what an
343    /// anonymous layer anchors against; [`resolved_path`](Self::resolved_path)
344    /// is the one that says so.
345    pub(crate) fn real_path(&self) -> &str {
346        self.real_path.as_deref().unwrap_or(&self.identifier)
347    }
348
349    /// The location that anchors the relative asset paths this layer authors,
350    /// or `None` when it is anonymous and so has no resolvable location (C++
351    /// `SdfLayer::GetRealPath` is empty for one).
352    pub(crate) fn anchor_location(&self) -> Option<ar::ResolvedPath> {
353        (!self.is_anonymous()).then(|| ar::ResolvedPath::new(self.real_path()))
354    }
355}
356
357impl Layer {
358    /// Write this layer to `filename`, choosing the format from the filename's
359    /// extension (C++ `SdfLayer::Export`). This writes a copy; the layer's own
360    /// identifier is unchanged.
361    ///
362    /// `.usda` writes text, `.usdc` writes the binary crate, `.usd` writes
363    /// binary (the C++ `USD_WRITE_NEW_USD_FILES_AS_BINARY` default), and
364    /// `.usdz` writes an archive wrapping one crate-encoded layer. The reader
365    /// auto-detects the format regardless of extension, so text written to a
366    /// `.usd` path still reads back correctly.
367    ///
368    /// The file is written beside the destination and renamed over it once
369    /// complete, keeping the destination's permissions. The destination is
370    /// therefore never a half-written file, and a file a layer reads from is
371    /// never written into. A destination this layer
372    /// itself reads from is written through [`save`](Self::save), which also
373    /// rebinds the layer to what it wrote.
374    pub fn export(&self, filename: impl AsRef<str>) -> Result<(), ExportError> {
375        let filename = filename.as_ref();
376        let format = super::LayerRegistry::export_format(filename)?;
377        let mut temp = tf::SafeOutputFile::create(FsPath::new(filename)).map_err(|source| ExportError::Create {
378            filename: filename.to_owned(),
379            source,
380        })?;
381        format.write(self.data(), temp.file())?;
382        temp.persist().map_err(|source| ExportError::Replace {
383            filename: filename.to_owned(),
384            source,
385        })
386    }
387
388    /// Serialize this layer to a `usda` text string (C++ `ExportToString`).
389    ///
390    /// Always emits text, the canonical human-readable form, regardless of the
391    /// layer's on-disk format. Named to avoid confusion with the infallible
392    /// [`ToString::to_string`].
393    pub fn export_to_string(&self) -> Result<String, ExportError> {
394        let format = super::LayerRegistry::find_by_id("usda").expect("usda is a built-in format");
395        let mut buf = Cursor::new(Vec::new());
396        format.write(self.data(), &mut buf)?;
397        Ok(String::from_utf8(buf.into_inner()).expect("the usda writer emits UTF-8 text"))
398    }
399
400    /// Write this layer to its own [`identifier`](Self::identifier), choosing
401    /// the format from the identifier's extension (C++ `SdfLayer::Save`).
402    ///
403    /// The identifier must be an absolute filesystem path — the canonical form
404    /// the resolver produces for layers loaded from disk.
405    /// Anonymous layers ([`is_anonymous`](Self::is_anonymous)) and layers whose
406    /// identifier is a relative path or a non-filesystem asset identifier (e.g.
407    /// `scheme://…`) have no persistent location here; save them with
408    /// [`export`](Self::export) and an explicit destination instead.
409    ///
410    /// A save is three steps, each leaving the layer whole if it fails. The
411    /// layer is serialized in memory and written to a temporary file beside
412    /// its own; a failure there changes nothing. The temporary file is then
413    /// renamed over the layer's own, which replaces it atomically. Last, a
414    /// layer whose data decodes from its bytes on demand (the crate format)
415    /// rebinds to the data its format built over the bytes it serialized
416    /// ([`FileFormat::write_bytes`](super::FileFormat::write_bytes)),
417    /// dropping the bytes it read before, which releases any view of its own
418    /// file it held. Data that copied everything out of its bytes stays as
419    /// it is.
420    ///
421    /// A rename that fails (a destination that is no longer a file, a
422    /// filesystem that refuses it) is [`ExportError::Replace`]: the temporary
423    /// file is removed and the layer still rebinds, which holds its edits in
424    /// memory for a later save to retry. Rebinding is not an edit: no change
425    /// list is derived and no sink fires.
426    ///
427    /// A layer opened through a resolver that maps files goes back onto a
428    /// mapping of the file it saved, and holds no heap copy of it (C++
429    /// `CrateFile::Packer::Close` reopens the saved asset through the resolver
430    /// and maps it again). The layer moves to the asset its resolver opens
431    /// only when that asset shares its bytes, the bytes are a file mapping,
432    /// and they equal the bytes just written. A resolver is free to answer
433    /// with something older than the file the save replaced, and data decoded
434    /// from that would put superseded values back in the layer; equal bytes
435    /// are what show the mapping to be the saved file. In every other case, a
436    /// failed open among them, the asset is dropped unread and the layer
437    /// stays on the bytes it serialized. The save has succeeded either way.
438    ///
439    /// A view another layer holds of the file does not block the
440    /// replacement: that layer goes on reading the file it mapped, which the
441    /// rename unlinks and leaves as it was.
442    pub fn save(&mut self) -> Result<(), ExportError> {
443        if self.is_anonymous() {
444            return Err(ExportError::Anonymous {
445                identifier: self.identifier.clone(),
446            });
447        }
448        let path = FsPath::new(&self.identifier);
449        if !path.is_absolute() {
450            return Err(ExportError::NotAFilePath {
451                identifier: self.identifier.clone(),
452            });
453        }
454        // Saving overwrites the layer's own file in place, so the format must
455        // support in-place editing — unlike `export`, which only writes a copy.
456        // A package format (usdz) is writable as a fresh archive but not
457        // editable in place: a loaded package's other assets (textures, sibling
458        // layers) are not held by the layer, so saving over it would discard
459        // them. An unknown extension is left for `export` to reject.
460        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_default();
461        if let Some(format) = super::LayerRegistry::find_by_extension(ext)
462            && !format.caps().can_edit()
463        {
464            return Err(ExportError::NotEditable {
465                identifier: self.identifier.clone(),
466                format: format.format_id(),
467            });
468        }
469        // TODO(layer-registry): save writes to `identifier` as a literal
470        // filesystem path. A layer loaded through a custom resolver may carry a
471        // logical identifier (e.g. `scheme://…`); save should resolve it through
472        // the owning resolver and write via a resolver write API. The
473        // absolute-path guard above refuses such identifiers for now. With
474        // the write and the reopen going through the one resolver, as C++
475        // `OpenAssetForWrite` then `OpenAsset` do, the reopen below would
476        // know the asset it opens is the one it wrote, and its byte
477        // comparison would have nothing left to prove.
478        // TODO(layer-registry): save re-selects the format from the identifier's
479        // extension, so a textual `.usd` layer is rewritten as binary. Once the
480        // load path records the FileFormat a layer was read with (C++
481        // `_fileFormat`), save should reuse it to preserve the original encoding.
482        let format = super::LayerRegistry::export_format(&self.identifier)?;
483        let written = format.write_bytes(self.data())?;
484        let create = |source| ExportError::Create {
485            filename: self.identifier.clone(),
486            source,
487        };
488        let mut temp = tf::SafeOutputFile::create(path).map_err(create)?;
489        temp.file().write_all(&written.bytes).map_err(create)?;
490        let replaced = temp.persist().map_err(|source| ExportError::Replace {
491            filename: self.identifier.clone(),
492            source,
493        });
494        if let Some(serialized) = written.data {
495            let reopened = match &replaced {
496                Ok(()) => self.reopen_saved(&written.bytes, format),
497                Err(_) => None,
498            };
499            self.data = CowData::new(reopened.unwrap_or(serialized));
500        }
501        replaced
502    }
503
504    /// The layer's data read from a mapping of its saved file, when the
505    /// resolver it was opened through serves one whose bytes are `written`,
506    /// the bytes just saved.
507    fn reopen_saved(&self, written: &ar::SharedBuffer, format: &dyn super::FileFormat) -> Option<LayerData> {
508        let resolved = ar::ResolvedPath::new(self.real_path());
509        let saved = self.resolver.as_ref()?.open_asset(&resolved).ok()?.shared_buffer()?;
510        if !saved.is_mapping() || saved[..] != written[..] {
511            return None;
512        }
513        format
514            .read_bytes(ar::AssetBuffer::Shared(saved), &resolved.to_string())
515            .ok()
516    }
517}
518
519/// Error returned by [`Layer::export`], [`Layer::export_to_string`], and
520/// [`Layer::save`].
521#[derive(Debug, thiserror::Error)]
522#[non_exhaustive]
523pub enum ExportError {
524    /// The destination path has no extension to choose a format from.
525    #[error("layer path {filename} has no extension; cannot choose format")]
526    NoExtension {
527        /// The destination path.
528        filename: String,
529    },
530
531    /// The destination path's extension names no registered format.
532    #[error("unsupported layer extension {extension:?} (expected usda/usdc/usd/usdz)")]
533    UnsupportedExtension {
534        /// The unrecognized extension.
535        extension: String,
536    },
537
538    /// The chosen format cannot serialize layer data at all.
539    #[error("file format {format} does not support writing")]
540    NotWritable {
541        /// The format's identifier token.
542        format: tf::Token,
543    },
544
545    /// [`Layer::save`] on an anonymous layer, which has no persistent
546    /// location.
547    #[error("cannot save anonymous layer {identifier}")]
548    Anonymous {
549        /// The layer's identifier.
550        identifier: String,
551    },
552
553    /// [`Layer::save`] on a layer whose identifier is not an absolute
554    /// filesystem path, so it names no writable location.
555    #[error(
556        "cannot save layer {identifier}: identifier is not an absolute file path; \
557         use Layer::export(path) to choose a destination"
558    )]
559    NotAFilePath {
560        /// The layer's identifier.
561        identifier: String,
562    },
563
564    /// [`Layer::save`] would overwrite the layer in place, but its format does
565    /// not support in-place editing.
566    #[error(
567        "cannot save layer {identifier} in place: the {format} format is not editable; \
568         use Layer::export(path) to write a new copy"
569    )]
570    NotEditable {
571        /// The layer's identifier.
572        identifier: String,
573        /// The format's identifier token.
574        format: tf::Token,
575    },
576
577    /// The destination file could not be created.
578    #[error("failed to create {filename}")]
579    Create {
580        /// The destination path.
581        filename: String,
582        /// The filesystem failure.
583        #[source]
584        source: io::Error,
585    },
586
587    /// The written file could not replace the destination, which is as it
588    /// was.
589    #[error("failed to replace {filename}")]
590    Replace {
591        /// The destination path.
592        filename: String,
593        /// The filesystem failure.
594        #[source]
595        source: io::Error,
596    },
597
598    /// The format failed to serialize the layer's data.
599    #[error(transparent)]
600    Format(#[from] FormatError),
601}
602
603/// Errors raised by [`Layer`]'s authoring methods.
604#[derive(Debug, thiserror::Error)]
605#[non_exhaustive]
606pub enum AuthoringError {
607    /// A path argument failed to parse.
608    #[error(transparent)]
609    Parse(#[from] PathParseError),
610
611    /// The target spec rejected an edit.
612    #[error(transparent)]
613    Spec(#[from] SpecError),
614
615    /// Reading a field from the source data failed (e.g. a lazy backend could
616    /// not decode an authored value while copying it).
617    #[error(transparent)]
618    Data(#[from] DataError),
619
620    /// A value does not fit the attribute's declared type, or the declaration
621    /// itself is unusable.
622    #[error(transparent)]
623    ValueType(#[from] ValueTypeError),
624
625    /// The given path is not valid for the requested authoring operation.
626    /// Prim authoring requires an absolute, non-root, non-property path;
627    /// property authoring requires a property path.
628    #[error("path {path} is not valid here: {reason}")]
629    InvalidPath {
630        /// The offending path.
631        path: Path,
632        /// A short, human-readable reason describing what was expected.
633        reason: &'static str,
634    },
635}
636
637/// An error from [`Layer::edit`]: the closure's authoring error, or a sink's
638/// rejection of the staged edit.
639#[derive(Debug, thiserror::Error)]
640pub enum EditError {
641    /// The edit closure failed to author; nothing was committed.
642    #[error(transparent)]
643    Author(#[from] AuthoringError),
644
645    /// A [`LayerSink`] rejected the staged edit from its
646    /// [`before_commit`](LayerSink::before_commit).
647    #[error(transparent)]
648    Rejected(#[from] sink::Error),
649}
650
651/// An observer of a single [`Layer`]'s committed edits — the low tier of the
652/// change pipeline, fired synchronously at the layer's commit seam so a sink
653/// sees every edit, by any editor, the moment it lands. A [`Stage`](crate::usd::Stage)
654/// installs an aggregating sink on each layer it owns to stay in sync;
655/// composition-level observation lives one tier up on the stage.
656///
657/// Both methods default to doing nothing, so a sink implements only the phase it
658/// cares about. Sinks fire in installation order.
659pub trait LayerSink {
660    /// Inspect a staged edit before it commits. Returning `Err` rolls the edit
661    /// back: the overlay is discarded and the error surfaces to the author. The
662    /// pristine base and the staged overlay are both visible, which is what lets
663    /// a sink derive an inverse (undo) record here.
664    fn before_commit(&self, change: &PendingLayerChange<'_>) -> Result<(), sink::Error> {
665        let _ = change;
666        Ok(())
667    }
668
669    /// Observe a committed edit, after the overlay has drained into the backend.
670    /// `layer` is the committing layer's identifier and `changes` the record
671    /// derived from the edit.
672    fn after_commit(&self, layer: &str, changes: &ChangeList) {
673        let _ = (layer, changes);
674    }
675}
676
677/// A bare closure is a [`LayerSink`] that only observes committed edits — the
678/// ergonomic "just record commits" case, installed straight through
679/// [`Layer::add_sink`] with no wrapper type. `Fn` (not `FnMut`) because a sink is
680/// invoked through a shared `&self`; capture interior-mutable state to accumulate.
681impl<F: Fn(&str, &ChangeList)> LayerSink for F {
682    fn after_commit(&self, layer: &str, changes: &ChangeList) {
683        self(layer, changes);
684    }
685}
686
687/// A staged, not-yet-committed edit on one layer, handed to
688/// [`LayerSink::before_commit`]: the pre-edit base, the staged overlay, and the
689/// derived change record — enough for a sink to derive a forward or inverse
690/// [`Edit`](crate::usd::Edit) log without committing.
691pub struct PendingLayerChange<'a> {
692    /// Canonical identifier of the edited layer.
693    pub layer_identifier: &'a str,
694    /// The layer's pre-edit values (the overlay's base).
695    pub base: &'a dyn AbstractData,
696    /// The staged changes keyed by path — the new values, as [`Patch`]es.
697    pub overlay: &'a HashMap<Path, Patch>,
698    /// The composition record derived from this layer's overlay. A sink that
699    /// needs to retain it past the callback clones it ([`ChangeList`] is
700    /// [`Clone`]).
701    pub change_list: &'a ChangeList,
702    /// The id of the atomic transaction (`edit_layers` group) this commit
703    /// belongs to: one id shared by every layer the transaction commits, distinct
704    /// from the next transaction's, and monotonically increasing. An observer
705    /// keys per-transaction state on it (see [`crate::usd::UndoStage`]).
706    pub generation: u64,
707}
708
709/// Identity, persistence, and typed-view lookups. Spec authoring lives on the
710/// views themselves ([`PrimSpec::new`](crate::sdf::PrimSpec::new) and friends,
711/// mirroring C++ `Sdf*Spec::New`); they write through [`LayerEdit::data_mut`], so
712/// every edit stages in the layer's overlay for the next flush to record.
713///
714/// The typed-view lookups ([`Layer::prim`], [`Layer::attribute`],
715/// [`Layer::relationship`], [`Layer::pseudo_root`]) operate through the
716/// [`AbstractData`] field interface, so they work uniformly on any backend —
717/// in-memory [`Data`] or a file-loaded reader — matching C++
718/// `SdfLayer::GetPrimAtPath`.
719impl Layer {
720    /// Create a blank in-memory writable layer with a unique anonymous
721    /// identifier of the form `anon:<n>:<tag>`, mirroring C++
722    /// `SdfLayer::CreateAnonymous`. `tag` is a human-readable hint, not an
723    /// address: two calls with the same tag get distinct identifiers, so
724    /// independent anonymous layers never alias (see [`Layer::is_anonymous`]).
725    ///
726    /// The layer's pseudo-root spec is pre-populated so layer-level metadata
727    /// (`defaultPrim`, `subLayers`, time codes, …) can be authored via
728    /// [`LayerEdit::pseudo_root_mut`] immediately.
729    pub fn new_anonymous(tag: impl fmt::Display) -> Self {
730        Self::new_in_memory(Self::anonymous_identifier(tag))
731    }
732
733    /// A fresh anonymous identifier of the form `anon:<n>:<tag>`. Distinct on
734    /// every call, so independent anonymous layers never alias.
735    pub(crate) fn anonymous_identifier(tag: impl fmt::Display) -> String {
736        // The `<n>` in `anon:<n>:<tag>` is unique in the process, so anonymous
737        // layers created anywhere in it never collide.
738        let n = crate::next_id();
739        format!("{ANONYMOUS_PREFIX}{n}:{tag}")
740    }
741
742    /// Create a blank in-memory writable layer with the given verbatim
743    /// identifier and a pre-populated pseudo-root spec, under the identifier
744    /// contract of [`new`](Self::new). Backs
745    /// [`new_anonymous`](Self::new_anonymous) and, within the crate, composes
746    /// layers whose identifiers must match an authored `subLayers` entry.
747    pub(crate) fn new_in_memory(identifier: impl Into<String>) -> Self {
748        let mut data = Data::new();
749        data.create_spec(Path::abs_root(), SpecType::PseudoRoot);
750        Self::new(identifier, Box::new(data))
751    }
752
753    /// Whether this layer's identifier is anonymous (the `anon:` prefix minted
754    /// by [`new_anonymous`](Self::new_anonymous)). Mirrors C++
755    /// `SdfLayer::IsAnonymous`; an anonymous layer has no asset-resolvable
756    /// location.
757    pub fn is_anonymous(&self) -> bool {
758        Self::is_anonymous_identifier(&self.identifier)
759    }
760
761    /// Whether `identifier` is an anonymous-layer identifier (the `anon:` prefix),
762    /// without needing the layer itself. Mirrors C++
763    /// `SdfLayer::IsAnonymousLayerIdentifier`; the prefix is the single source of
764    /// truth for anonymity.
765    pub fn is_anonymous_identifier(identifier: &str) -> bool {
766        identifier.starts_with(ANONYMOUS_PREFIX)
767    }
768
769    /// Look up a prim spec at `path`. `Ok(None)` when no spec exists there or
770    /// the spec is not a prim; `Err` when `path` fails to parse.
771    pub fn prim(&self, path: impl IntoPath) -> Result<Option<PrimSpecRef<'_>>, PathParseError> {
772        Ok(PrimSpecRef::get(self.data(), try_into_path(path)?))
773    }
774
775    /// Look up an attribute spec at a property path.
776    pub fn attribute(&self, path: impl IntoPath) -> Result<Option<AttributeSpecRef<'_>>, PathParseError> {
777        Ok(AttributeSpecRef::get(self.data(), try_into_path(path)?))
778    }
779
780    /// Look up a relationship spec at a property path.
781    pub fn relationship(&self, path: impl IntoPath) -> Result<Option<RelationshipSpecRef<'_>>, PathParseError> {
782        Ok(RelationshipSpecRef::get(self.data(), try_into_path(path)?))
783    }
784
785    /// View this layer's root pseudo-spec, which carries layer-wide metadata
786    /// (`defaultPrim`, `subLayers`, time codes, …).
787    pub fn pseudo_root(&self) -> Option<PseudoRootSpecRef<'_>> {
788        PseudoRootSpecRef::get(self.data())
789    }
790
791    /// The list of namespace relocations authored in this layer's metadata,
792    /// or an empty list when none are authored. Mirrors C++
793    /// `SdfLayer::GetRelocates`. Reads through the backing [`AbstractData`], so
794    /// it works on both in-memory and file-loaded layers.
795    pub fn relocates(&self) -> RelocateList {
796        self.pseudo_root().and_then(|root| root.relocates()).unwrap_or_default()
797    }
798
799    /// Whether this layer's metadata carries any `relocates` opinion, including
800    /// an explicit empty list (an opinion that *there should be no* relocates).
801    /// Mirrors C++ `SdfLayer::HasRelocates`.
802    pub fn has_relocates(&self) -> bool {
803        self.has_root_field(FieldKey::LayerRelocates)
804    }
805
806    /// The layer's `startTimeCode`, or `0.0` when unauthored. Mirrors C++
807    /// `SdfLayer::GetStartTimeCode`.
808    pub fn start_time_code(&self) -> f64 {
809        self.pseudo_root()
810            .and_then(|root| root.start_time_code())
811            .unwrap_or(0.0)
812    }
813
814    /// Whether this layer authors a `startTimeCode` opinion. Mirrors C++
815    /// `SdfLayer::HasStartTimeCode`.
816    pub fn has_start_time_code(&self) -> bool {
817        self.has_root_field(FieldKey::StartTimeCode)
818    }
819
820    /// The layer's `endTimeCode`, or `0.0` when unauthored. Mirrors C++
821    /// `SdfLayer::GetEndTimeCode`.
822    pub fn end_time_code(&self) -> f64 {
823        self.pseudo_root().and_then(|root| root.end_time_code()).unwrap_or(0.0)
824    }
825
826    /// Whether this layer authors an `endTimeCode` opinion. Mirrors C++
827    /// `SdfLayer::HasEndTimeCode`.
828    pub fn has_end_time_code(&self) -> bool {
829        self.has_root_field(FieldKey::EndTimeCode)
830    }
831
832    /// The layer's `timeCodesPerSecond`. Falls back to the authored
833    /// `framesPerSecond`, then to `24.0`, when unauthored. Mirrors C++
834    /// `SdfLayer::GetTimeCodesPerSecond`.
835    pub fn time_codes_per_second(&self) -> f64 {
836        let root = self.pseudo_root();
837        root.as_ref()
838            .and_then(|root| root.time_codes_per_second())
839            .or_else(|| root.as_ref().and_then(|root| root.frames_per_second()))
840            .unwrap_or(24.0)
841    }
842
843    /// Whether this layer authors a `timeCodesPerSecond` opinion. Mirrors C++
844    /// `SdfLayer::HasTimeCodesPerSecond`.
845    pub fn has_time_codes_per_second(&self) -> bool {
846        self.has_root_field(FieldKey::TimeCodesPerSecond)
847    }
848
849    /// The layer's `framesPerSecond`, or `24.0` when unauthored. Mirrors C++
850    /// `SdfLayer::GetFramesPerSecond`.
851    pub fn frames_per_second(&self) -> f64 {
852        self.pseudo_root()
853            .and_then(|root| root.frames_per_second())
854            .unwrap_or(24.0)
855    }
856
857    /// Whether this layer authors a `framesPerSecond` opinion. Mirrors C++
858    /// `SdfLayer::HasFramesPerSecond`.
859    pub fn has_frames_per_second(&self) -> bool {
860        self.has_root_field(FieldKey::FramesPerSecond)
861    }
862
863    /// The layer's `framePrecision`, or `3` when unauthored. Mirrors C++
864    /// `SdfLayer::GetFramePrecision`.
865    pub fn frame_precision(&self) -> i32 {
866        self.pseudo_root().and_then(|root| root.frame_precision()).unwrap_or(3)
867    }
868
869    /// Whether this layer authors a `framePrecision` opinion. Mirrors C++
870    /// `SdfLayer::HasFramePrecision`.
871    pub fn has_frame_precision(&self) -> bool {
872        self.has_root_field(FieldKey::FramePrecision)
873    }
874
875    /// Whether the pseudo-root spec authors `key`, including an explicit
876    /// opinion that carries an "empty"/default value.
877    fn has_root_field(&self, key: FieldKey) -> bool {
878        self.data.has_field(&Path::abs_root(), key.as_str())
879    }
880}
881
882/// The authoring view of a [`Layer`] handed to an [`edit`](Layer::edit) closure
883/// — the only surface through which a layer is mutated.
884///
885/// Derefs to the [`Layer`] for reads, and exposes the layer's authoring
886/// operations directly. A layer's `commit` and `rollback` are deliberately absent:
887/// the edit commits when the closure returns and rolls back on its error or a
888/// panic, so an edit can never be left staged-but-uncommitted, and a multi-layer
889/// edit stays atomic across its layers.
890pub struct LayerEdit<'a> {
891    layer: &'a mut Layer,
892}
893
894impl std::ops::Deref for LayerEdit<'_> {
895    type Target = Layer;
896
897    fn deref(&self) -> &Layer {
898        &*self.layer
899    }
900}
901
902impl LayerEdit<'_> {
903    /// Borrow the layer's backing data mutably for authoring; the write stages in
904    /// the copy-on-write overlay and commits with the enclosing edit. The typed
905    /// spec constructors ([`PrimSpec::new`](crate::sdf::PrimSpec::new) and friends)
906    /// write through this.
907    pub fn data_mut(&mut self) -> &mut dyn AbstractData {
908        &mut self.layer.data
909    }
910
911    /// Remove the spec at `path`. Removing a prim also erases its descendant specs
912    /// and drops the leaf name from the owning prim's child-name list. Returns
913    /// `Ok(true)` when a spec was present and removed.
914    pub fn remove_spec(&mut self, path: &Path) -> Result<bool, AuthoringError> {
915        super::spec::remove_spec(self.data_mut(), path)
916    }
917
918    /// Mutably look up the prim spec at `path`.
919    pub fn prim_mut(&mut self, path: impl IntoPath) -> Result<Option<PrimSpecMut<'_>>, PathParseError> {
920        Ok(PrimSpecMut::get(self.data_mut(), try_into_path(path)?))
921    }
922
923    /// Mutably look up the attribute spec at a property path.
924    pub fn attribute_mut(&mut self, path: impl IntoPath) -> Result<Option<AttributeSpecMut<'_>>, PathParseError> {
925        Ok(AttributeSpecMut::get(self.data_mut(), try_into_path(path)?))
926    }
927
928    /// Mutably look up the relationship spec at a property path.
929    pub fn relationship_mut(&mut self, path: impl IntoPath) -> Result<Option<RelationshipSpecMut<'_>>, PathParseError> {
930        Ok(RelationshipSpecMut::get(self.data_mut(), try_into_path(path)?))
931    }
932
933    /// Mutably view the root pseudo-spec, which carries layer-wide metadata
934    /// (`defaultPrim`, `subLayers`, time codes, …). The spec is created on first
935    /// access if missing.
936    pub fn pseudo_root_mut(&mut self) -> Result<PseudoRootSpecMut<'_>, AuthoringError> {
937        let root = Path::abs_root();
938        match self.layer.data().spec_type(&root) {
939            Some(SpecType::PseudoRoot) => {}
940            Some(_) => {
941                return Err(AuthoringError::InvalidPath {
942                    path: root,
943                    reason: "root spec exists with non-PseudoRoot SpecType",
944                });
945            }
946            None => self.data_mut().create_spec(root, SpecType::PseudoRoot),
947        }
948        Ok(PseudoRootSpecMut::get(self.data_mut()).expect("just ensured a pseudo-root spec"))
949    }
950
951    /// Set the layer's `defaultPrim` metadata to `name` — a prim identifier, a
952    /// nested prim path, or either spelled absolutely (`"World"`,
953    /// `"World/Char"`, `"/World/Char"`). The token is stored as given;
954    /// [`default_prim_path`] reads both spellings.
955    ///
956    /// This is a checked authoring API, which is a deliberate divergence: C++
957    /// `SdfLayer::SetDefaultPrim` stores any token and defers every check to
958    /// `GetDefaultPrimAsPath`. Rejecting here keeps a value that names no prim
959    /// out of the layer in the first place; the spec-tier
960    /// [`PseudoRootSpecMut::set_default_prim`] remains the unvalidated escape
961    /// hatch for round-tripping data verbatim.
962    pub fn set_default_prim(&mut self, name: impl Into<String>) -> Result<(), AuthoringError> {
963        let name = name.into();
964        if default_prim_path(&name).is_none() {
965            return Err(AuthoringError::InvalidPath {
966                path: Path::abs_root(),
967                reason: "defaultPrim must name a prim",
968            });
969        }
970        self.pseudo_root_mut()?.set_default_prim(name);
971        Ok(())
972    }
973
974    /// Remove the layer's `defaultPrim` metadata. No-op when no pseudo-root spec
975    /// exists.
976    pub fn clear_default_prim(&mut self) -> Result<(), AuthoringError> {
977        self.clear_root_field(FieldKey::DefaultPrim)
978    }
979
980    /// Set the layer's entire list of namespace relocations. An empty list authors
981    /// an explicit "no relocates" opinion; use [`clear_relocates`](Self::clear_relocates)
982    /// to remove the opinion entirely.
983    pub fn set_relocates(&mut self, relocates: RelocateList) -> Result<(), AuthoringError> {
984        self.pseudo_root_mut()?.set_relocates(relocates);
985        Ok(())
986    }
987
988    /// Clear the layer's `relocates` opinion. No-op when no pseudo-root spec exists.
989    pub fn clear_relocates(&mut self) -> Result<(), AuthoringError> {
990        self.clear_root_field(FieldKey::LayerRelocates)
991    }
992
993    /// Replace the layer's `expressionVariables` dictionary, the values an
994    /// `${VAR}` expression resolves against (see
995    /// [`PseudoRootSpec::set_expression_variables`](crate::sdf::PseudoRootSpec::set_expression_variables)).
996    pub fn set_expression_variables(&mut self, vars: HashMap<String, Value>) -> Result<(), AuthoringError> {
997        self.pseudo_root_mut()?.set_expression_variables(vars);
998        Ok(())
999    }
1000
1001    /// Clear the layer's `expressionVariables` opinion. No-op when no pseudo-root spec exists.
1002    pub fn clear_expression_variables(&mut self) -> Result<(), AuthoringError> {
1003        self.clear_root_field(FieldKey::ExpressionVariables)
1004    }
1005
1006    /// Set the layer's `startTimeCode`.
1007    pub fn set_start_time_code(&mut self, time: f64) -> Result<(), AuthoringError> {
1008        self.pseudo_root_mut()?.set_start_time_code(time);
1009        Ok(())
1010    }
1011
1012    /// Clear the layer's `startTimeCode` opinion. No-op when no pseudo-root spec exists.
1013    pub fn clear_start_time_code(&mut self) -> Result<(), AuthoringError> {
1014        self.clear_root_field(FieldKey::StartTimeCode)
1015    }
1016
1017    /// Set the layer's `endTimeCode`.
1018    pub fn set_end_time_code(&mut self, time: f64) -> Result<(), AuthoringError> {
1019        self.pseudo_root_mut()?.set_end_time_code(time);
1020        Ok(())
1021    }
1022
1023    /// Clear the layer's `endTimeCode` opinion. No-op when no pseudo-root spec exists.
1024    pub fn clear_end_time_code(&mut self) -> Result<(), AuthoringError> {
1025        self.clear_root_field(FieldKey::EndTimeCode)
1026    }
1027
1028    /// Set the layer's `timeCodesPerSecond`.
1029    pub fn set_time_codes_per_second(&mut self, rate: f64) -> Result<(), AuthoringError> {
1030        self.pseudo_root_mut()?.set_time_codes_per_second(rate);
1031        Ok(())
1032    }
1033
1034    /// Clear the layer's `timeCodesPerSecond` opinion. No-op when no pseudo-root spec exists.
1035    pub fn clear_time_codes_per_second(&mut self) -> Result<(), AuthoringError> {
1036        self.clear_root_field(FieldKey::TimeCodesPerSecond)
1037    }
1038
1039    /// Set the layer's `framesPerSecond`.
1040    pub fn set_frames_per_second(&mut self, rate: f64) -> Result<(), AuthoringError> {
1041        self.pseudo_root_mut()?.set_frames_per_second(rate);
1042        Ok(())
1043    }
1044
1045    /// Clear the layer's `framesPerSecond` opinion. No-op when no pseudo-root spec exists.
1046    pub fn clear_frames_per_second(&mut self) -> Result<(), AuthoringError> {
1047        self.clear_root_field(FieldKey::FramesPerSecond)
1048    }
1049
1050    /// Set the layer's `framePrecision`.
1051    pub fn set_frame_precision(&mut self, precision: i32) -> Result<(), AuthoringError> {
1052        self.pseudo_root_mut()?.set_frame_precision(precision);
1053        Ok(())
1054    }
1055
1056    /// Clear the layer's `framePrecision` opinion. No-op when no pseudo-root spec exists.
1057    pub fn clear_frame_precision(&mut self) -> Result<(), AuthoringError> {
1058        self.clear_root_field(FieldKey::FramePrecision)
1059    }
1060
1061    /// Remove a metadata field from the pseudo-root spec, if that spec exists. No-op
1062    /// otherwise — clearing what isn't there must not materialize state.
1063    fn clear_root_field(&mut self, key: FieldKey) -> Result<(), AuthoringError> {
1064        // `erase_field` is a no-op when the pseudo-root spec is absent.
1065        self.data_mut().erase_field(&Path::abs_root(), key.as_str());
1066        Ok(())
1067    }
1068}
1069
1070impl std::fmt::Debug for Layer {
1071    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072        f.debug_struct("Layer")
1073            .field("identifier", &self.identifier)
1074            .finish_non_exhaustive()
1075    }
1076}
1077
1078/// The prim path a `defaultPrim` token names, or `None` when it names no prim.
1079///
1080/// Mirrors C++ `SdfLayer::ConvertDefaultPrimTokenToPath`: the token may be
1081/// absolute or relative, a relative one is anchored at the pseudo-root, and it
1082/// must name a prim. C++ writes a nested default prim absolutely
1083/// (`ConvertDefaultPrimPathToToken` emits a bare name only for a root prim), so
1084/// accepting both spellings is what lets this port read a C++-authored layer.
1085///
1086/// ```text
1087/// "World"       -> Some("/World")
1088/// "World/Char"  -> Some("/World/Char")
1089/// "/World/Char" -> Some("/World/Char")
1090/// "World.attr"  -> None
1091/// "/"           -> None
1092/// ""            -> None
1093/// ```
1094///
1095/// The relative anchors `.` and `..` yield `None`. For `..` that matches C++,
1096/// which anchors it past the root to an empty path. For `.` it does not: C++
1097/// anchors it to `/` and composes a pseudo-root arc that contributes nothing.
1098/// The composed result is the same either way, so this port reports the
1099/// unusable value instead of resolving it to a target no reference can use.
1100pub fn default_prim_path(token: &str) -> Option<Path> {
1101    let path = Path::new(token).ok()?;
1102    // No identifier may begin with `.`, so the only prim paths whose text starts
1103    // with one are the relative anchors `.`, `..`, and `../…`.
1104    if !path.is_prim_path() || path.as_str().starts_with('.') {
1105        return None;
1106    }
1107    Some(Path::abs_root().make_absolute(&path))
1108}
1109
1110/// Author across several layers as one atomic transaction: run `f` to stage
1111/// edits into every layer through its [`LayerEdit`] view, then commit them
1112/// together. Commit is two-phase — every layer's
1113/// [`before_commit`](LayerSink::before_commit) runs first, and only once all
1114/// accept does any layer's overlay drain into its backend — so a single vetoing
1115/// sink, an authoring error from `f`, or a panic rolls every layer back, leaving
1116/// none partially applied. Returns whether any layer's edit produced a
1117/// composition change. Each committed layer fires its sinks, so the change records
1118/// reach a stage through its aggregator rather than through this return.
1119///
1120/// [`Layer::edit`] is the single-layer case of this.
1121pub(crate) fn edit_layers<E: From<sink::Error>>(
1122    layers: &mut [&mut Layer],
1123    f: impl FnOnce(&mut [LayerEdit<'_>]) -> Result<(), E>,
1124) -> Result<bool, E> {
1125    let mut guard = GroupEditGuard {
1126        layers,
1127        committed: false,
1128    };
1129    {
1130        let mut edits: Vec<LayerEdit<'_>> = guard.layers.iter_mut().map(|layer| LayerEdit { layer }).collect();
1131        f(&mut edits)?;
1132        // `edits` drops at the block end, releasing the per-layer borrows for the
1133        // commit phase below.
1134    }
1135    // One id for the whole atomic group, so an observer sees every layer this
1136    // transaction commits under a single transaction id.
1137    let generation = crate::next_id();
1138    // Phase 1: refill each layer's record and offer it to that layer's
1139    // `before_commit`; a veto aborts before any layer commits.
1140    for layer in guard.layers.iter_mut() {
1141        layer.prepare_commit(generation)?;
1142    }
1143    // Phase 2: every layer accepted, so drain each overlay into its backend. Commit
1144    // all layers before notifying any, so the whole group's data lands even if a
1145    // later `after_commit` panics.
1146    let mut changed = false;
1147    for layer in guard.layers.iter_mut() {
1148        changed |= layer.commit_data();
1149    }
1150    // The group is committed, so the guard's drop leaves every overlay in place.
1151    guard.committed = true;
1152    // Phase 3: deliver each layer's `after_commit`. A panic here interrupts only
1153    // notification — the group's data is already committed.
1154    for layer in guard.layers.iter() {
1155        layer.notify_after_commit();
1156    }
1157    Ok(changed)
1158}
1159
1160/// Stage edits across `layers` to verify they apply, then discard them all — the
1161/// dry-run counterpart of [`edit_layers`]. Returns `f`'s authoring result while
1162/// committing nothing and firing no sink, so a caller can check that an edit is
1163/// valid (e.g. a namespace edit's `can_apply`) without mutating the layers or
1164/// notifying observers.
1165pub(crate) fn dry_run_layers<E>(
1166    layers: &mut [&mut Layer],
1167    f: impl FnOnce(&mut [LayerEdit<'_>]) -> Result<(), E>,
1168) -> Result<(), E> {
1169    // `committed` stays false, so the guard rolls every layer back on the way out,
1170    // whether `f` succeeds or fails — a dry run leaves no staged state behind.
1171    let guard = GroupEditGuard {
1172        layers,
1173        committed: false,
1174    };
1175    let mut edits: Vec<LayerEdit<'_>> = guard.layers.iter_mut().map(|layer| LayerEdit { layer }).collect();
1176    f(&mut edits)
1177}
1178
1179/// Rolls every layer in an [`edit_layers`] group back unless the group commits —
1180/// on an authoring error, a sink veto, or a panic — so a failed multi-layer edit
1181/// strands no layer with a half-applied overlay.
1182struct GroupEditGuard<'a, 'b> {
1183    layers: &'b mut [&'a mut Layer],
1184    committed: bool,
1185}
1186
1187impl Drop for GroupEditGuard<'_, '_> {
1188    fn drop(&mut self) {
1189        if self.committed {
1190            return;
1191        }
1192        for layer in self.layers.iter_mut() {
1193            layer.rollback();
1194        }
1195    }
1196}
1197
1198#[cfg(test)]
1199mod tests {
1200    use super::*;
1201    use crate::Result;
1202
1203    use std::cell::{Cell, RefCell};
1204    use std::fs;
1205    use std::io;
1206    use std::path::PathBuf;
1207    use std::rc::Rc;
1208    use std::sync::Arc;
1209    use std::sync::atomic::{AtomicUsize, Ordering};
1210
1211    use crate::sdf;
1212    use crate::usda;
1213
1214    /// A layer opened by any spelling of its path keeps the canonical
1215    /// identifier, so it stays one identity and `save` finds the file it loaded
1216    /// from. Every other load path canonicalizes first; this one must too.
1217    #[test]
1218    fn open_canonicalizes_identifier() -> Result<()> {
1219        let dir = tempfile::tempdir()?;
1220        let path = dir.path().join("scene.usda");
1221        std::fs::write(&path, "#usda 1.0\n")?;
1222
1223        // The same file, spelled with a redundant directory step.
1224        let indirect = dir.path().join(".").join("scene.usda");
1225        let mut layer = Layer::open(indirect.to_string_lossy())?;
1226
1227        assert_eq!(
1228            layer.identifier(),
1229            path.canonicalize()?.to_string_lossy(),
1230            "the identifier is the canonical one, not the spelling passed in"
1231        );
1232        layer
1233            .save()
1234            .expect("a layer that loaded from a file can save back to it");
1235        Ok(())
1236    }
1237
1238    /// Author through the layer's `edit` API and commit, for tests that build a
1239    /// layer's content or metadata directly.
1240    fn edit_layer(layer: &mut Layer, f: impl FnOnce(&mut LayerEdit<'_>)) {
1241        layer
1242            .edit(|e| {
1243                f(e);
1244                Ok(())
1245            })
1246            .expect("authored");
1247    }
1248
1249    /// A crate layer written to `dir`: a prim with a kind-less spec and one
1250    /// array attribute the layer decodes on demand.
1251    fn crate_fixture(dir: &FsPath) -> Result<PathBuf> {
1252        let path = dir.join("scene.usdc");
1253        let text = format!(
1254            "#usda 1.0\ndef \"World\"\n{{\n    float[] size = [{}]\n}}\n",
1255            ["1"; 64].join(", ")
1256        );
1257        Layer::from_bytes("scene", text.into_bytes())?.export(path.to_string_lossy())?;
1258        Ok(path)
1259    }
1260
1261    /// The `kind` a layer answers for `/World`.
1262    fn world_kind(layer: &Layer) -> Option<tf::Token> {
1263        layer.prim("/World").unwrap().expect("the prim exists").kind()
1264    }
1265
1266    /// Authors `kind = "component"` on `/World`, the edit the save tests
1267    /// check survives.
1268    fn set_component(layer: &mut Layer) {
1269        edit_layer(layer, |e| {
1270            e.prim_mut("/World")
1271                .unwrap()
1272                .expect("the prim exists")
1273                .set_kind("component");
1274        });
1275    }
1276
1277    /// Saving a crate layer over its own file keeps the edit and the fields
1278    /// the layer decodes on demand, and leaves no temporary file behind.
1279    #[test]
1280    fn save_crate_over_itself() -> Result<()> {
1281        let dir = tempfile::tempdir()?;
1282        let path = crate_fixture(dir.path())?;
1283        let mut layer = Layer::open(path.to_string_lossy())?;
1284        set_component(&mut layer);
1285        layer.save()?;
1286
1287        let component = Some(tf::Token::from("component"));
1288        assert_eq!(world_kind(&layer), component);
1289        let size = layer
1290            .data()
1291            .get_field(&Path::new("/World.size")?, "default")
1292            .expect("decoded on demand")
1293            .into_owned();
1294        assert_eq!(size, Value::FloatVec(vec![1.0; 64]));
1295        assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
1296        assert_eq!(fs::read_dir(dir.path())?.count(), 1, "no temporary file remains");
1297        Ok(())
1298    }
1299
1300    /// A replacement that fails leaves the edit in the layer and no temporary
1301    /// file behind, and the next save succeeds once the way is clear.
1302    #[test]
1303    fn replace_failure_keeps_edits() -> Result<()> {
1304        let dir = tempfile::tempdir()?;
1305        let path = crate_fixture(dir.path())?;
1306        let mut layer = Layer::open(path.to_string_lossy())?;
1307        set_component(&mut layer);
1308        // The destination becomes a directory, which the temporary file cannot
1309        // be renamed over, after the file was written.
1310        fs::remove_file(&path)?;
1311        fs::create_dir(&path)?;
1312        let error = layer.save().expect_err("the replacement fails");
1313        assert!(matches!(error, ExportError::Replace { .. }), "{error:?}");
1314        let component = Some(tf::Token::from("component"));
1315        assert_eq!(world_kind(&layer), component, "the edit survives in memory");
1316        assert_eq!(fs::read_dir(dir.path())?.count(), 1, "no temporary file remains");
1317
1318        fs::remove_dir(&path)?;
1319        layer.save()?;
1320        assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
1321        Ok(())
1322    }
1323
1324    /// Exporting over a symbolic link replaces the file the link reaches and
1325    /// leaves the link in place.
1326    #[cfg(unix)]
1327    #[test]
1328    fn export_through_symlink() -> Result<()> {
1329        use std::os::unix::fs::symlink;
1330        let dir = tempfile::tempdir()?;
1331        let target = crate_fixture(dir.path())?;
1332        let link = dir.path().join("link.usdc");
1333        symlink(&target, &link)?;
1334        let mut layer = Layer::open(target.to_string_lossy())?;
1335        set_component(&mut layer);
1336        layer.export(link.to_string_lossy())?;
1337        assert!(
1338            fs::symlink_metadata(&link)?.file_type().is_symlink(),
1339            "the link is kept"
1340        );
1341        let component = Some(tf::Token::from("component"));
1342        assert_eq!(world_kind(&Layer::open(target.to_string_lossy())?), component);
1343        Ok(())
1344    }
1345
1346    /// Replacing a file on Windows keeps its access control list: a layer
1347    /// whose list is explicit and uninherited stays so after a save and an
1348    /// export over it.
1349    #[cfg(windows)]
1350    #[test]
1351    fn save_keeps_acl() -> Result<()> {
1352        use std::env;
1353        use std::process::Command;
1354        let dir = tempfile::tempdir()?;
1355        let path = crate_fixture(dir.path())?;
1356        let file = path.to_str().expect("utf-8 temp path");
1357        let icacls = |args: &[&str]| -> Result<String> {
1358            let output = Command::new("icacls").args(args).output()?;
1359            assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
1360            Ok(String::from_utf8_lossy(&output.stdout).into_owned())
1361        };
1362        let grant = format!("{}:(R,W)", env::var("USERNAME").expect("USERNAME is set"));
1363        icacls(&[file, "/inheritance:r", "/grant:r", &grant])?;
1364        let before = icacls(&[file])?;
1365        assert!(before.contains("(R,W)"), "{before}");
1366
1367        let mut layer = Layer::open(file)?;
1368        set_component(&mut layer);
1369        layer.save()?;
1370        assert_eq!(icacls(&[file])?, before, "the list survives the save");
1371        layer.export(file)?;
1372        assert_eq!(icacls(&[file])?, before, "the list survives the export");
1373        assert_eq!(world_kind(&Layer::open(file)?), Some(tf::Token::from("component")));
1374        Ok(())
1375    }
1376
1377    /// Replacing a file keeps its permissions: a private layer stays private
1378    /// after a save and after an export over it.
1379    #[cfg(unix)]
1380    #[test]
1381    fn save_keeps_permissions() -> Result<()> {
1382        use std::os::unix::fs::PermissionsExt;
1383        let dir = tempfile::tempdir()?;
1384        let path = crate_fixture(dir.path())?;
1385        fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
1386        let mut layer = Layer::open(path.to_string_lossy())?;
1387        layer.save()?;
1388        assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o777, 0o600);
1389        layer.export(path.to_string_lossy())?;
1390        assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o777, 0o600);
1391        Ok(())
1392    }
1393
1394    /// An export over an existing file leaves only that file behind, and one
1395    /// that cannot create its temporary file leaves nothing.
1396    #[test]
1397    fn export_leaves_no_temp() -> Result<()> {
1398        let dir = tempfile::tempdir()?;
1399        let path = crate_fixture(dir.path())?;
1400        let layer = Layer::open(path.to_string_lossy())?;
1401        layer.export(path.to_string_lossy())?;
1402        assert_eq!(fs::read_dir(dir.path())?.count(), 1);
1403        let missing = dir.path().join("missing").join("scene.usdc");
1404        let error = layer.export(missing.to_string_lossy()).expect_err("no such directory");
1405        assert!(matches!(error, ExportError::Create { .. }), "{error:?}");
1406        assert_eq!(fs::read_dir(dir.path())?.count(), 1);
1407        Ok(())
1408    }
1409
1410    /// A view another layer holds does not block a save: the file is replaced
1411    /// under it, the other layer goes on reading the bytes it mapped, and a
1412    /// fresh open sees the edit.
1413    #[cfg(feature = "mmap")]
1414    #[test]
1415    // The mapping opt-in is `unsafe` by contract, and this test, the only
1416    // writer of its directory, can make the promise.
1417    #[allow(unsafe_code)]
1418    fn save_with_other_view() -> Result<()> {
1419        let dir = tempfile::tempdir()?;
1420        let path = crate_fixture(dir.path())?;
1421        // SAFETY: only the save below writes the files in this directory, and
1422        // a save never writes into a file it holds a view of: it replaces the
1423        // file beside it.
1424        let resolver = unsafe { ar::DefaultResolver::new().map_files() };
1425        let registry = crate::sdf::LayerRegistry::new(Box::new(resolver));
1426        let identifier = registry.create_identifier(&path.to_string_lossy(), None);
1427        let mut editing = registry.open_layer(&identifier)?.expect("the layer resolves");
1428        let other = registry.open_layer(&identifier)?.expect("the layer resolves");
1429        set_component(&mut editing);
1430
1431        editing.save()?;
1432        let component = Some(tf::Token::from("component"));
1433        assert_eq!(world_kind(&editing), component);
1434        assert_eq!(world_kind(&other), None, "the other view reads the file it mapped");
1435        let size = other
1436            .data()
1437            .get_field(&Path::new("/World.size")?, "default")
1438            .expect("still decodes from its view")
1439            .into_owned();
1440        assert_eq!(size, Value::FloatVec(vec![1.0; 64]));
1441        assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
1442        Ok(())
1443    }
1444
1445    /// `new_anonymous` mints a unique `anon:<n>:<tag>` identifier each call, so
1446    /// two layers given the same tag never alias; `is_anonymous` recognizes them
1447    /// while a verbatim `new_in_memory` layer is not anonymous.
1448    #[test]
1449    fn anonymous_identifiers_unique() {
1450        let a = Layer::new_anonymous("scene.usda");
1451        let b = Layer::new_anonymous("scene.usda");
1452        assert_ne!(a.identifier(), b.identifier());
1453        assert!(a.identifier().ends_with(":scene.usda"));
1454        assert!(a.is_anonymous() && b.is_anonymous());
1455        assert!(!Layer::new_in_memory("scene.usda").is_anonymous());
1456    }
1457
1458    /// `set_relocates` / `relocates` round-trip the authored pairs, and the
1459    /// pairs land under the pseudo-root's `layerRelocates` field.
1460    #[test]
1461    fn relocates_round_trip() {
1462        let mut layer = Layer::new_anonymous("test.usda");
1463        assert!(!layer.has_relocates());
1464        assert!(layer.relocates().is_empty());
1465
1466        let pairs = vec![
1467            (Path::new("/Rig/Model").unwrap(), Path::new("/Group/Model").unwrap()),
1468            (Path::new("/Rig/Dead").unwrap(), Path::default()),
1469        ];
1470        edit_layer(&mut layer, |e| {
1471            e.set_relocates(pairs.clone()).unwrap();
1472        });
1473
1474        assert!(layer.has_relocates());
1475        assert_eq!(layer.relocates(), pairs);
1476    }
1477
1478    /// An explicit empty list is still an opinion (`HasRelocates` is true), while
1479    /// `clear_relocates` removes the opinion entirely.
1480    #[test]
1481    fn empty_opinion_vs_cleared() {
1482        let mut layer = Layer::new_anonymous("test.usda");
1483
1484        edit_layer(&mut layer, |e| {
1485            e.set_relocates(RelocateList::new()).unwrap();
1486        });
1487        assert!(layer.has_relocates());
1488        assert!(layer.relocates().is_empty());
1489
1490        edit_layer(&mut layer, |e| {
1491            e.clear_relocates().unwrap();
1492        });
1493        assert!(!layer.has_relocates());
1494        assert!(layer.relocates().is_empty());
1495    }
1496
1497    /// The five time-code fields round-trip through set/get and report the
1498    /// documented unauthored defaults before any opinion is written.
1499    #[test]
1500    fn time_code_round_trip() {
1501        let mut layer = Layer::new_anonymous("test.usda");
1502        assert!(!layer.has_start_time_code());
1503        assert_eq!(layer.start_time_code(), 0.0);
1504        assert_eq!(layer.end_time_code(), 0.0);
1505        assert_eq!(layer.frame_precision(), 3);
1506
1507        edit_layer(&mut layer, |e| {
1508            e.set_start_time_code(1.0).unwrap();
1509            e.set_end_time_code(48.0).unwrap();
1510            e.set_frame_precision(6).unwrap();
1511        });
1512
1513        assert!(layer.has_start_time_code());
1514        assert_eq!(layer.start_time_code(), 1.0);
1515        assert_eq!(layer.end_time_code(), 48.0);
1516        assert_eq!(layer.frame_precision(), 6);
1517    }
1518
1519    /// `timeCodesPerSecond` falls back to the authored `framesPerSecond`, then
1520    /// to `24.0`, when no `timeCodesPerSecond` opinion is authored.
1521    #[test]
1522    fn tcps_fps_fallback() {
1523        let mut layer = Layer::new_anonymous("test.usda");
1524        assert_eq!(layer.time_codes_per_second(), 24.0);
1525        assert_eq!(layer.frames_per_second(), 24.0);
1526
1527        edit_layer(&mut layer, |e| {
1528            e.set_frames_per_second(30.0).unwrap();
1529        });
1530        assert_eq!(layer.time_codes_per_second(), 30.0);
1531
1532        edit_layer(&mut layer, |e| {
1533            e.set_time_codes_per_second(48.0).unwrap();
1534        });
1535        assert_eq!(layer.time_codes_per_second(), 48.0);
1536    }
1537
1538    /// `clear_*` removes the opinion so `has_*` reports false and the getter
1539    /// returns the unauthored default again.
1540    #[test]
1541    fn clear_time_code() {
1542        let mut layer = Layer::new_anonymous("test.usda");
1543        edit_layer(&mut layer, |e| {
1544            e.set_start_time_code(5.0).unwrap();
1545        });
1546        assert!(layer.has_start_time_code());
1547
1548        edit_layer(&mut layer, |e| {
1549            e.clear_start_time_code().unwrap();
1550        });
1551        assert!(!layer.has_start_time_code());
1552        assert_eq!(layer.start_time_code(), 0.0);
1553    }
1554
1555    /// `edit` commits the batch and returns the recorded `ChangeList`.
1556    #[test]
1557    fn edit_commits() {
1558        use crate::sdf::{PrimSpec, Specifier};
1559        let mut layer = Layer::new_anonymous("test.usda");
1560        let changed = layer
1561            .edit(|l| {
1562                PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1563                Ok(())
1564            })
1565            .expect("no error or veto");
1566        assert!(changed);
1567        assert!(layer.prim(Path::new("/World").unwrap()).unwrap().is_some());
1568    }
1569
1570    /// `edit` rolls the batch back when the closure returns an error, leaving the
1571    /// layer untouched and reusable.
1572    #[test]
1573    fn edit_rolls_back_on_error() {
1574        use crate::sdf::{PrimSpec, Specifier};
1575        let mut layer = Layer::new_anonymous("test.usda");
1576        let result: Result<bool, EditError> = layer.edit(|l| {
1577            PrimSpec::new(l.data_mut(), "/A", Specifier::Def, "Xform")?;
1578            Err(AuthoringError::InvalidPath {
1579                path: Path::abs_root(),
1580                reason: "test",
1581            })
1582        });
1583        assert!(result.is_err());
1584        assert!(
1585            layer.prim(Path::new("/A").unwrap()).unwrap().is_none(),
1586            "the staged edit rolled back"
1587        );
1588        assert!(!layer.is_dirty());
1589    }
1590
1591    /// A panic mid-`edit` unwinds cleanly: the guard rolls the staged edits back,
1592    /// so the layer is reusable afterwards.
1593    #[test]
1594    fn edit_recovers_on_panic() {
1595        use crate::sdf::{PrimSpec, Specifier};
1596        use std::panic::{AssertUnwindSafe, catch_unwind};
1597
1598        let mut layer = Layer::new_anonymous("test.usda");
1599        let panicked = catch_unwind(AssertUnwindSafe(|| {
1600            let _: Result<bool, EditError> = layer.edit(|l| {
1601                PrimSpec::new(l.data_mut(), "/A", Specifier::Def, "Xform").expect("authored");
1602                panic!("boom");
1603            });
1604        }));
1605        assert!(panicked.is_err(), "the panic propagates");
1606        assert!(
1607            layer.prim(Path::new("/A").unwrap()).unwrap().is_none(),
1608            "the staged edit rolled back"
1609        );
1610        assert!(!layer.is_dirty(), "no staged edits or recorded changes remain");
1611
1612        // The layer is still usable afterwards.
1613        let changed = layer
1614            .edit(|l| {
1615                PrimSpec::new(l.data_mut(), "/B", Specifier::Def, "Xform")?;
1616                Ok(())
1617            })
1618            .expect("no error");
1619        assert!(changed);
1620        assert!(layer.prim(Path::new("/B").unwrap()).unwrap().is_some());
1621    }
1622
1623    /// A panicking `after_commit` cannot strand a partially committed group:
1624    /// `edit_layers` commits every layer's data before notifying any sink, so both
1625    /// layers land even when one's `after_commit` unwinds.
1626    #[test]
1627    fn group_commit_atomic_on_panic() {
1628        use crate::sdf::{PrimSpec, Specifier};
1629        use std::panic::{AssertUnwindSafe, catch_unwind};
1630
1631        let mut a = Layer::new_anonymous("a.usda");
1632        let mut b = Layer::new_anonymous("b.usda");
1633        a.add_sink(|_: &str, _: &ChangeList| panic!("boom"));
1634
1635        let panicked = catch_unwind(AssertUnwindSafe(|| {
1636            let _ = edit_layers(&mut [&mut a, &mut b], |edits| {
1637                PrimSpec::new(edits[0].data_mut(), "/A", Specifier::Def, "Xform").expect("authored a");
1638                PrimSpec::new(edits[1].data_mut(), "/B", Specifier::Def, "Xform").expect("authored b");
1639                Ok::<(), EditError>(())
1640            });
1641        }));
1642        assert!(panicked.is_err(), "the after_commit panic propagates");
1643        assert!(
1644            a.prim(Path::new("/A").unwrap()).unwrap().is_some(),
1645            "layer a committed despite the panic"
1646        );
1647        assert!(
1648            b.prim(Path::new("/B").unwrap()).unwrap().is_some(),
1649            "layer b committed despite the panic"
1650        );
1651    }
1652
1653    /// `discard_changes` keeps committed content in the backend and leaves the
1654    /// layer non-dirty, so a layer joining a stage is composed fresh with no stale
1655    /// change record.
1656    #[test]
1657    fn discard_changes_keeps_content() {
1658        use crate::sdf::{PrimSpec, Specifier};
1659
1660        let mut layer = Layer::new_anonymous("test.usda");
1661        edit_layer(&mut layer, |e| {
1662            PrimSpec::new(e.data_mut(), "/World", Specifier::Def, "Xform").unwrap();
1663        });
1664
1665        layer.discard_changes();
1666        assert!(!layer.is_dirty());
1667        assert!(
1668            layer.prim(Path::new("/World").unwrap()).unwrap().is_some(),
1669            "the authored content survives"
1670        );
1671    }
1672
1673    /// The C++ `ConvertDefaultPrimTokenToPath` contract
1674    /// (`pxr/usd/sdf/testenv/testSdfLayer.py`), plus the one documented
1675    /// difference: `.` resolves to `/` there and to nothing here.
1676    #[test]
1677    fn default_prim_path_forms() {
1678        for (token, want) in [
1679            ("/foo", "/foo"),
1680            ("/foo/bar", "/foo/bar"),
1681            ("foo", "/foo"),
1682            ("foo/bar", "/foo/bar"),
1683        ] {
1684            assert_eq!(default_prim_path(token).as_ref().map(Path::as_str), Some(want));
1685        }
1686        for token in ["/foo.prop", "/", "//", "", "..", "."] {
1687            assert_eq!(default_prim_path(token), None, "{token} names no prim");
1688        }
1689    }
1690
1691    /// The setter accepts either spelling of a prim path and rejects a value
1692    /// that names no prim, matching what `default_prim_path` will read back.
1693    #[test]
1694    fn set_default_prim_forms() {
1695        let mut layer = Layer::new_anonymous("anon.usda");
1696        for name in ["World", "World/Char", "/World/Char"] {
1697            edit_layer(&mut layer, |e| {
1698                e.set_default_prim(name).expect("names a prim");
1699            });
1700            assert_eq!(layer.pseudo_root().unwrap().default_prim().as_deref(), Some(name));
1701        }
1702        edit_layer(&mut layer, |e| {
1703            assert!(e.set_default_prim("World.attr").is_err(), "a property names no prim");
1704            assert!(e.set_default_prim("/").is_err(), "the pseudo-root names no prim");
1705        });
1706    }
1707
1708    /// Authoring layer metadata on a backend that lacks a pseudo-root spec
1709    /// materializes the pseudo-root, so a follow-up read sees the field; clearing
1710    /// the opinion removes it again.
1711    #[test]
1712    fn metadata_on_rootless_backend() {
1713        let root = Path::abs_root();
1714        let default_prim = FieldKey::DefaultPrim.as_str();
1715
1716        let mut layer = Layer::new("rootless.usda", Box::new(Data::new()));
1717        edit_layer(&mut layer, |e| {
1718            e.set_default_prim("World").unwrap();
1719        });
1720        assert!(layer.data().has_field(&root, default_prim));
1721
1722        edit_layer(&mut layer, |e| {
1723            e.clear_default_prim().unwrap();
1724        });
1725        assert!(!layer.data().has_field(&root, default_prim));
1726    }
1727
1728    /// A recording [`LayerSink`] for the tests below.
1729    #[derive(Default)]
1730    struct Recorder {
1731        before: std::cell::Cell<u32>,
1732        after: std::cell::Cell<u32>,
1733        veto: bool,
1734    }
1735
1736    impl LayerSink for std::rc::Rc<Recorder> {
1737        fn before_commit(&self, _change: &PendingLayerChange<'_>) -> Result<(), sink::Error> {
1738            self.before.set(self.before.get() + 1);
1739            if self.veto {
1740                return Err(sink::Error::new("vetoed"));
1741            }
1742            Ok(())
1743        }
1744        fn after_commit(&self, _layer: &str, _changes: &ChangeList) {
1745            self.after.set(self.after.get() + 1);
1746        }
1747    }
1748
1749    /// A committed edit fires the layer sink's `before_commit` then
1750    /// `after_commit` once, with the edit landed.
1751    #[test]
1752    fn layer_sink_fires() {
1753        use crate::sdf::{PrimSpec, Specifier};
1754        let mut layer = Layer::new_anonymous("test.usda");
1755        let rec = std::rc::Rc::new(Recorder::default());
1756        layer.add_sink(rec.clone());
1757
1758        layer
1759            .edit(|l| {
1760                PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1761                Ok(())
1762            })
1763            .expect("sink accepts");
1764
1765        assert_eq!((rec.before.get(), rec.after.get()), (1, 1));
1766        assert!(layer.prim(Path::new("/World").unwrap()).unwrap().is_some());
1767    }
1768
1769    /// A `before_commit` rejection rolls the edit back: `after_commit` never
1770    /// fires and the backend is untouched.
1771    #[test]
1772    fn layer_sink_veto_rolls_back() {
1773        use crate::sdf::{PrimSpec, Specifier};
1774        let mut layer = Layer::new_anonymous("test.usda");
1775        let rec = std::rc::Rc::new(Recorder {
1776            veto: true,
1777            ..Default::default()
1778        });
1779        layer.add_sink(rec.clone());
1780
1781        let result = layer.edit(|l| {
1782            PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1783            Ok(())
1784        });
1785        assert!(matches!(result, Err(EditError::Rejected(_))), "the sink vetoes");
1786
1787        assert_eq!((rec.before.get(), rec.after.get()), (1, 0), "after_commit never fired");
1788        assert!(
1789            layer.prim(Path::new("/World").unwrap()).unwrap().is_none(),
1790            "the edit rolled back"
1791        );
1792        assert!(!layer.is_dirty());
1793    }
1794
1795    /// A removed sink no longer fires.
1796    #[test]
1797    fn layer_sink_removed() {
1798        use crate::sdf::{PrimSpec, Specifier};
1799        let mut layer = Layer::new_anonymous("test.usda");
1800        let rec = std::rc::Rc::new(Recorder::default());
1801        let id = layer.add_sink(rec.clone());
1802        layer.remove_sink(id);
1803
1804        layer
1805            .edit(|l| {
1806                PrimSpec::new(l.data_mut(), "/World", Specifier::Def, "Xform")?;
1807                Ok(())
1808            })
1809            .expect("no sink");
1810        assert_eq!((rec.before.get(), rec.after.get()), (0, 0));
1811    }
1812
1813    #[test]
1814    fn export_dispatches_on_extension() -> Result<()> {
1815        use crate::sdf::{self, SpecType};
1816
1817        let mut data = sdf::Data::new();
1818        let root = sdf::Path::abs_root();
1819        let ps = data.create_spec(root, SpecType::PseudoRoot);
1820        ps.add("primChildren", sdf::Value::TokenVec(vec!["Foo".into()]));
1821        let foo = sdf::path("/Foo")?;
1822        let sp = data.create_spec(foo, SpecType::Prim);
1823        sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
1824        sp.add("typeName", sdf::Value::Token("Xform".into()));
1825
1826        let layer = sdf::Layer::new("test://layer", Box::new(data));
1827        let dir = tempfile::tempdir()?;
1828
1829        let usda_path = dir.path().join("layer-save.usda");
1830        let usdc_path = dir.path().join("layer-save.usdc");
1831        let usdz_path = dir.path().join("layer-save.usdz");
1832
1833        layer.export(usda_path.to_str().unwrap())?;
1834        layer.export(usdc_path.to_str().unwrap())?;
1835        layer.export(usdz_path.to_str().unwrap())?;
1836
1837        assert!(std::fs::metadata(&usda_path)?.len() > 0);
1838        assert!(std::fs::metadata(&usdc_path)?.len() > 0);
1839        assert!(std::fs::metadata(&usdz_path)?.len() > 0);
1840
1841        // The usdz should contain an entry we can read back.
1842        let archive = crate::usdz::Archive::open(&usdz_path)?;
1843        let name = archive.first_layer_name().expect("usdz has a layer");
1844        assert!(name.ends_with(".usdc"));
1845        Ok(())
1846    }
1847
1848    #[test]
1849    fn export_usd_writes_binary() -> Result<()> {
1850        use crate::sdf::{self, SpecType};
1851
1852        let mut data = sdf::Data::new();
1853        let root = sdf::Path::abs_root();
1854        let ps = data.create_spec(root, SpecType::PseudoRoot);
1855        ps.add("primChildren", sdf::Value::TokenVec(vec!["Bar".into()]));
1856        let bar = sdf::path("/Bar")?;
1857        let sp = data.create_spec(bar.clone(), SpecType::Prim);
1858        sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
1859        sp.add("typeName", sdf::Value::Token("Cube".into()));
1860
1861        let layer = sdf::Layer::new("test://layer-usd", Box::new(data));
1862        let dir = tempfile::tempdir()?;
1863        let path = dir.path().join("layer-save.usd");
1864        layer.export(path.to_str().unwrap())?;
1865
1866        // Writer chose binary for `.usd` — first bytes must be the USDC magic.
1867        let bytes = std::fs::read(&path)?;
1868        assert!(
1869            bytes.starts_with(crate::usdc::MAGIC),
1870            "writer should emit binary for .usd, got magic {:?}",
1871            &bytes[..crate::usdc::MAGIC.len().min(bytes.len())],
1872        );
1873
1874        // The registry's content-sniff must accept it as binary.
1875        let round = sdf::LayerRegistry::default()
1876            .open(path.to_str().unwrap())?
1877            .expect("the layer opens")
1878            .data;
1879        assert_eq!(round.spec_type(&bar), Some(SpecType::Prim));
1880        assert_eq!(
1881            round.get_field(&bar, "typeName").unwrap().into_owned(),
1882            sdf::Value::Token("Cube".into())
1883        );
1884        Ok(())
1885    }
1886
1887    #[test]
1888    fn export_rejects_unknown_ext() {
1889        use crate::sdf::{self, SpecType};
1890        let mut data = sdf::Data::new();
1891        data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1892        let layer = sdf::Layer::new("test://layer", Box::new(data));
1893        let err = layer.export("/tmp/openusd-bad.xyz").unwrap_err();
1894        assert!(err.to_string().contains("unsupported"));
1895    }
1896
1897    #[test]
1898    fn save_writes_identifier() -> Result<()> {
1899        use crate::sdf::{self, SpecType};
1900
1901        let mut data = sdf::Data::new();
1902        data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1903
1904        let dir = tempfile::tempdir()?;
1905        let path = dir.path().join("by-identifier.usda");
1906        let mut layer = sdf::Layer::new(path.to_str().unwrap(), Box::new(data));
1907        layer.save()?;
1908
1909        let bytes = std::fs::read(&path)?;
1910        assert!(
1911            bytes.starts_with(b"#usda"),
1912            "save() should write text to a .usda identifier"
1913        );
1914        Ok(())
1915    }
1916
1917    #[test]
1918    fn save_rejects_anonymous() {
1919        let mut layer = sdf::Layer::new_anonymous("scratch");
1920        let err = layer.save().unwrap_err();
1921        assert!(err.to_string().contains("anonymous"));
1922    }
1923
1924    #[test]
1925    fn save_rejects_non_file_identifier() {
1926        use crate::sdf::{self, SpecType};
1927        let mut data = sdf::Data::new();
1928        data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1929        // A non-anonymous layer whose identifier is not an absolute file path
1930        // (a scheme-style asset identifier) cannot be saved to its identifier.
1931        let mut layer = sdf::Layer::new("scheme://host/x.usda", Box::new(data));
1932        let err = layer.save().unwrap_err();
1933        assert!(err.to_string().contains("absolute file path"));
1934    }
1935
1936    #[test]
1937    fn save_rejects_usdz_in_place() {
1938        use crate::sdf::{self, SpecType};
1939        let mut data = sdf::Data::new();
1940        data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1941        // usdz is writable as a new copy but not editable in place: saving over
1942        // a package would discard its other assets, so save() must refuse it.
1943        let dir = tempfile::tempdir().unwrap();
1944        let path = dir.path().join("pkg.usdz");
1945        let mut layer = sdf::Layer::new(path.to_str().unwrap(), Box::new(data));
1946        let err = layer.save().unwrap_err();
1947        assert!(err.to_string().contains("not editable"));
1948    }
1949
1950    #[test]
1951    fn export_to_string_is_usda() -> Result<()> {
1952        use crate::sdf::{self, SpecType};
1953
1954        let mut data = sdf::Data::new();
1955        let ps = data.create_spec(sdf::Path::abs_root(), SpecType::PseudoRoot);
1956        ps.add("primChildren", sdf::Value::TokenVec(vec!["Foo".into()]));
1957        let sp = data.create_spec(sdf::path("/Foo")?, SpecType::Prim);
1958        sp.add("specifier", sdf::Value::Specifier(sdf::Specifier::Def));
1959
1960        // A binary identifier must not influence the textual form.
1961        let layer = sdf::Layer::new("scratch.usdc", Box::new(data));
1962        let text = layer.export_to_string()?;
1963        assert!(text.starts_with("#usda"), "export_to_string must emit usda text");
1964        assert!(text.contains("\"Foo\""), "export_to_string must include the prim");
1965        Ok(())
1966    }
1967
1968    // -----------------------------------------------------------------------
1969    // Authoring API
1970    // -----------------------------------------------------------------------
1971
1972    #[test]
1973    fn create_prim_basic() -> Result<()> {
1974        let mut layer = sdf::Layer::new_anonymous("anon.usda");
1975        edit_layer(&mut layer, |e| {
1976            sdf::PrimSpec::new(e.data_mut(), "/World", sdf::Specifier::Def, "Xform")
1977                .unwrap()
1978                .set_kind("group");
1979        });
1980
1981        let world = layer.prim("/World")?.expect("prim authored");
1982        assert_eq!(world.type_name().as_deref(), Some("Xform"));
1983        assert_eq!(world.specifier(), Some(sdf::Specifier::Def));
1984        assert_eq!(world.kind().as_deref(), Some("group"));
1985        Ok(())
1986    }
1987
1988    #[test]
1989    fn auto_ancestor_chain() -> Result<()> {
1990        let mut layer = sdf::Layer::new_anonymous("anon.usda");
1991        edit_layer(&mut layer, |e| {
1992            sdf::PrimSpec::new(e.data_mut(), "/A/B/C", sdf::Specifier::Def, "Mesh").unwrap();
1993        });
1994
1995        // Leaf is Def; ancestors are Over.
1996        assert_eq!(
1997            layer.prim("/A/B/C")?.and_then(|p| p.specifier()),
1998            Some(sdf::Specifier::Def)
1999        );
2000        assert_eq!(
2001            layer.prim("/A/B")?.and_then(|p| p.specifier()),
2002            Some(sdf::Specifier::Over)
2003        );
2004        assert_eq!(
2005            layer.prim("/A")?.and_then(|p| p.specifier()),
2006            Some(sdf::Specifier::Over)
2007        );
2008        Ok(())
2009    }
2010
2011    #[test]
2012    fn prim_children() -> Result<()> {
2013        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2014        edit_layer(&mut layer, |e| {
2015            sdf::PrimSpec::new(e.data_mut(), "/World", sdf::Specifier::Def, "Xform").unwrap();
2016            sdf::PrimSpec::new(e.data_mut(), "/World/Mesh", sdf::Specifier::Def, "Mesh").unwrap();
2017            sdf::PrimSpec::new(e.data_mut(), "/World/Cube", sdf::Specifier::Def, "Cube").unwrap();
2018        });
2019
2020        let root = layer.pseudo_root().expect("pseudo-root present");
2021        let root_children = root.prim_children().unwrap();
2022        let root_children: Vec<&str> = root_children.iter().map(|t| t.as_str()).collect();
2023        assert_eq!(root_children, ["World"]);
2024
2025        let world = layer.prim("/World")?.expect("prim");
2026        let world_children = world.prim_children().unwrap();
2027        let world_children: Vec<&str> = world_children.iter().map(|t| t.as_str()).collect();
2028        assert_eq!(world_children, ["Mesh", "Cube"]);
2029        Ok(())
2030    }
2031
2032    #[test]
2033    fn property_children() -> Result<()> {
2034        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2035        edit_layer(&mut layer, |e| {
2036            sdf::PrimSpec::new(e.data_mut(), "/Mesh", sdf::Specifier::Def, "Mesh").unwrap();
2037            sdf::AttributeSpec::new(
2038                e.data_mut(),
2039                "/Mesh.points",
2040                "point3f[]",
2041                sdf::Variability::Varying,
2042                false,
2043            )
2044            .unwrap();
2045            sdf::AttributeSpec::new(
2046                e.data_mut(),
2047                "/Mesh.normals",
2048                "normal3f[]",
2049                sdf::Variability::Varying,
2050                false,
2051            )
2052            .unwrap();
2053            sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Varying, false)
2054                .unwrap();
2055        });
2056
2057        let mesh = layer.prim("/Mesh")?.expect("prim");
2058        let props = mesh.property_children().unwrap();
2059        let props: Vec<&str> = props.iter().map(|t| t.as_str()).collect();
2060        assert_eq!(props, ["points", "normals", "material:binding"]);
2061        Ok(())
2062    }
2063
2064    #[test]
2065    fn relationship_variability() -> Result<()> {
2066        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2067        edit_layer(&mut layer, |e| {
2068            sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Uniform, false)
2069                .unwrap();
2070            // A declaration is never rewritten: creating over the spec is an error.
2071            let error =
2072                sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Varying, false)
2073                    .expect_err("a spec already at the path");
2074            assert!(matches!(error, sdf::AuthoringError::InvalidPath { .. }));
2075            sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.varying", sdf::Variability::Varying, false).unwrap();
2076        });
2077
2078        let rel = layer.relationship("/Mesh.material:binding")?.expect("relationship");
2079        assert_eq!(rel.variability(), sdf::Variability::Uniform);
2080        // Varying is the fallback, so it is never written out.
2081        let rel = layer.relationship("/Mesh.varying")?.expect("relationship");
2082        assert_eq!(rel.variability(), sdf::Variability::Varying);
2083        assert!(rel.field(sdf::FieldKey::Variability.as_str()).ok().flatten().is_none());
2084        Ok(())
2085    }
2086
2087    #[test]
2088    fn bad_prim_children_errors() {
2089        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2090        edit_layer(&mut layer, |e| {
2091            e.data_mut().set_field(
2092                &sdf::Path::abs_root(),
2093                sdf::ChildrenKey::PrimChildren.as_str(),
2094                sdf::Value::String("bad".into()),
2095            );
2096        });
2097
2098        let err = layer
2099            .edit(|e| {
2100                sdf::PrimSpec::new(e.data_mut(), "/A", sdf::Specifier::Def, "Xform")?;
2101                Ok(())
2102            })
2103            .unwrap_err();
2104        assert!(matches!(
2105            err,
2106            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2107        ));
2108
2109        let value = layer
2110            .data()
2111            .get_field(&sdf::Path::abs_root(), sdf::ChildrenKey::PrimChildren.as_str())
2112            .unwrap()
2113            .into_owned();
2114        assert!(matches!(value, sdf::Value::String(value) if value == "bad"));
2115    }
2116
2117    #[test]
2118    fn bad_property_children_errors() -> Result<()> {
2119        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2120        edit_layer(&mut layer, |e| {
2121            sdf::PrimSpec::new(e.data_mut(), "/Mesh", sdf::Specifier::Def, "Mesh").unwrap();
2122            e.data_mut().set_field(
2123                &sdf::path("/Mesh").unwrap(),
2124                sdf::ChildrenKey::PropertyChildren.as_str(),
2125                sdf::Value::String("bad".into()),
2126            );
2127        });
2128
2129        let err = layer
2130            .edit(|e| {
2131                sdf::RelationshipSpec::new(e.data_mut(), "/Mesh.material:binding", sdf::Variability::Varying, false)?;
2132                Ok(())
2133            })
2134            .unwrap_err();
2135        assert!(matches!(
2136            err,
2137            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2138        ));
2139
2140        assert!(!layer.data().has_spec(&sdf::path("/Mesh.material:binding").unwrap()));
2141        let value = layer
2142            .data()
2143            .get_field(
2144                &sdf::path("/Mesh").unwrap(),
2145                sdf::ChildrenKey::PropertyChildren.as_str(),
2146            )
2147            .unwrap()
2148            .into_owned();
2149        assert!(matches!(value, sdf::Value::String(value) if value == "bad"));
2150        Ok(())
2151    }
2152
2153    #[test]
2154    fn attr_samples() -> Result<()> {
2155        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2156        edit_layer(&mut layer, |e| {
2157            sdf::PrimSpec::new(e.data_mut(), "/Sphere", sdf::Specifier::Def, "Sphere").unwrap();
2158            let mut radius = sdf::AttributeSpec::new(
2159                e.data_mut(),
2160                "/Sphere.radius",
2161                "double",
2162                sdf::Variability::Varying,
2163                false,
2164            )
2165            .unwrap();
2166            radius
2167                .set_default(sdf::Value::Double(2.5))
2168                .expect("value fits the declared type");
2169            radius
2170                .set_time_sample(0.0, sdf::Value::Double(1.0))
2171                .expect("value fits the declared type");
2172            radius
2173                .set_time_sample(10.0, sdf::Value::Double(3.0))
2174                .expect("value fits the declared type");
2175            // Out-of-order insert lands in sorted position.
2176            radius
2177                .set_time_sample(5.0, sdf::Value::Double(2.0))
2178                .expect("value fits the declared type");
2179        });
2180
2181        let read = layer.attribute("/Sphere.radius")?.expect("attr");
2182        assert_eq!(read.type_name(), Some(sdf::ValueTypeName::DOUBLE));
2183        assert_eq!(read.default(), Some(sdf::Value::Double(2.5)));
2184        let samples = read.time_samples().expect("samples authored");
2185        let times: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
2186        assert_eq!(times, vec![0.0, 5.0, 10.0]);
2187        Ok(())
2188    }
2189
2190    #[test]
2191    fn wrong_ancestor_type() {
2192        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2193        // Plant a non-Prim spec at a prim-shaped path through the data API.
2194        edit_layer(&mut layer, |e| {
2195            e.data_mut()
2196                .create_spec(sdf::path("/A").unwrap(), sdf::SpecType::Attribute);
2197        });
2198
2199        let err = layer
2200            .edit(|e| {
2201                sdf::PrimSpec::new(e.data_mut(), "/A/B", sdf::Specifier::Def, "Xform")?;
2202                Ok(())
2203            })
2204            .unwrap_err();
2205        assert!(matches!(
2206            err,
2207            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2208        ));
2209        let err = layer
2210            .edit(|e| {
2211                sdf::AttributeSpec::new(e.data_mut(), "/A.x", "double", sdf::Variability::Varying, false)?;
2212                Ok(())
2213            })
2214            .unwrap_err();
2215        assert!(matches!(
2216            err,
2217            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2218        ));
2219    }
2220
2221    #[test]
2222    fn failed_prim_chain_creation_is_atomic() {
2223        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2224        let bad_child = sdf::path("/A/B").unwrap();
2225        edit_layer(&mut layer, |e| {
2226            e.data_mut().create_spec(bad_child, sdf::SpecType::Attribute);
2227        });
2228
2229        let err = layer
2230            .edit(|e| {
2231                sdf::PrimSpec::new(e.data_mut(), "/A/B", sdf::Specifier::Def, "Xform")?;
2232                Ok(())
2233            })
2234            .unwrap_err();
2235        assert!(matches!(
2236            err,
2237            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2238        ));
2239
2240        assert!(!layer.data().has_spec(&sdf::path("/A").unwrap()));
2241        assert!(
2242            layer
2243                .data()
2244                .try_field(&sdf::Path::abs_root(), sdf::ChildrenKey::PrimChildren.as_str())
2245                .unwrap()
2246                .is_none()
2247        );
2248    }
2249
2250    #[test]
2251    fn nan_time_sample() -> Result<()> {
2252        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2253        edit_layer(&mut layer, |e| {
2254            sdf::PrimSpec::new(e.data_mut(), "/Sphere", sdf::Specifier::Def, "Sphere").unwrap();
2255            let mut r = sdf::AttributeSpec::new(
2256                e.data_mut(),
2257                "/Sphere.radius",
2258                "double",
2259                sdf::Variability::Varying,
2260                false,
2261            )
2262            .unwrap();
2263            r.set_time_sample(1.0, sdf::Value::Double(1.0))
2264                .expect("value fits the declared type");
2265            r.set_time_sample(f64::NAN, sdf::Value::Double(99.0))
2266                .expect("value fits the declared type");
2267            r.set_time_sample(2.0, sdf::Value::Double(2.0))
2268                .expect("value fits the declared type");
2269        });
2270        // NaN does not collide with finite samples — both finite values survive.
2271        let samples = layer
2272            .attribute("/Sphere.radius")?
2273            .unwrap()
2274            .time_samples()
2275            .unwrap()
2276            .clone();
2277        let finite: Vec<f64> = samples.iter().map(|(t, _)| *t).filter(|t| t.is_finite()).collect();
2278        assert_eq!(finite, vec![1.0, 2.0]);
2279
2280        // erase_time_sample(NaN) finds the NaN entry: the sample-time order
2281        // places a NaN, so it is a locatable time like any other.
2282        let mut erased = false;
2283        edit_layer(&mut layer, |e| {
2284            erased = e
2285                .attribute_mut("/Sphere.radius")
2286                .unwrap()
2287                .unwrap()
2288                .erase_time_sample(f64::NAN)
2289                .expect("samples readable");
2290        });
2291        assert!(erased);
2292        let times: Vec<f64> = layer
2293            .attribute("/Sphere.radius")?
2294            .unwrap()
2295            .time_samples()
2296            .unwrap()
2297            .iter()
2298            .map(|(t, _)| *t)
2299            .collect();
2300        assert_eq!(times, vec![1.0, 2.0]);
2301        Ok(())
2302    }
2303
2304    #[test]
2305    fn override_prim() -> Result<()> {
2306        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2307        edit_layer(&mut layer, |e| {
2308            sdf::PrimSpec::over(e.data_mut(), "/A/B").unwrap();
2309        });
2310
2311        assert_eq!(
2312            layer.prim("/A")?.and_then(|p| p.specifier()),
2313            Some(sdf::Specifier::Over)
2314        );
2315        assert_eq!(
2316            layer.prim("/A/B")?.and_then(|p| p.specifier()),
2317            Some(sdf::Specifier::Over)
2318        );
2319
2320        // override_prim on an existing def leaves the specifier untouched.
2321        edit_layer(&mut layer, |e| {
2322            sdf::PrimSpec::new(e.data_mut(), "/Defined", sdf::Specifier::Def, "Xform").unwrap();
2323            sdf::PrimSpec::over(e.data_mut(), "/Defined").unwrap();
2324        });
2325        assert_eq!(
2326            layer.prim("/Defined")?.and_then(|p| p.specifier()),
2327            Some(sdf::Specifier::Def)
2328        );
2329        Ok(())
2330    }
2331
2332    #[test]
2333    fn pseudo_root_metadata() -> Result<()> {
2334        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2335        edit_layer(&mut layer, |e| {
2336            let mut root = e.pseudo_root_mut().expect("writable");
2337            root.set_default_prim("World");
2338            root.set_documentation("auto-generated");
2339            root.set_start_time_code(1.0);
2340            root.set_end_time_code(24.0);
2341            root.add_sublayer("./over.usda");
2342            root.add_sublayer("./over.usda");
2343        });
2344        let root = layer.pseudo_root().expect("present");
2345        assert_eq!(root.default_prim().as_deref(), Some("World"));
2346        assert_eq!(root.documentation().as_deref(), Some("auto-generated"));
2347        assert_eq!(root.start_time_code(), Some(1.0));
2348        assert_eq!(root.end_time_code(), Some(24.0));
2349        assert_eq!(
2350            root.sublayers(),
2351            Some(vec!["./over.usda".to_string(), "./over.usda".to_string()])
2352        );
2353        Ok(())
2354    }
2355
2356    #[test]
2357    fn invalid_paths() {
2358        let mut layer = sdf::Layer::new_anonymous("anon.usda");
2359        // Whether authoring `f` fails with an `InvalidPath` authoring error.
2360        fn invalid(
2361            layer: &mut sdf::Layer,
2362            f: impl FnOnce(&mut sdf::LayerEdit<'_>) -> Result<(), sdf::AuthoringError>,
2363        ) -> bool {
2364            matches!(
2365                layer.edit(f).unwrap_err(),
2366                sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2367            )
2368        }
2369        assert!(invalid(&mut layer, |e| {
2370            sdf::PrimSpec::new(e.data_mut(), "/", sdf::Specifier::Def, "Xform")?;
2371            Ok(())
2372        }));
2373        assert!(invalid(&mut layer, |e| {
2374            sdf::PrimSpec::new(e.data_mut(), "/A.foo", sdf::Specifier::Def, "Xform")?;
2375            Ok(())
2376        }));
2377        assert!(invalid(&mut layer, |e| {
2378            sdf::AttributeSpec::new(e.data_mut(), "/A", "double", sdf::Variability::Varying, false)?;
2379            Ok(())
2380        }));
2381        // Relative property paths must error, not panic in ensure_prim_chain.
2382        assert!(invalid(&mut layer, |e| {
2383            sdf::AttributeSpec::new(e.data_mut(), "A.foo", "double", sdf::Variability::Varying, false)?;
2384            Ok(())
2385        }));
2386        assert!(invalid(&mut layer, |e| {
2387            sdf::RelationshipSpec::new(e.data_mut(), "A.foo", sdf::Variability::Varying, false)?;
2388            Ok(())
2389        }));
2390        // Root-level property paths (`/.foo`) are not parseable at all, so
2391        // they surface as a parse error rather than an authoring rejection.
2392        assert!(matches!(
2393            layer
2394                .edit(|e| {
2395                    sdf::AttributeSpec::new(e.data_mut(), "/.foo", "double", sdf::Variability::Varying, false)?;
2396                    Ok(())
2397                })
2398                .unwrap_err(),
2399            sdf::EditError::Author(sdf::AuthoringError::Parse(_))
2400        ));
2401        // Target-bracket property paths slip past `is_property_path` because the
2402        // tail after the last `.` is alphanumeric — split_property_path must reject them.
2403        assert!(invalid(&mut layer, |e| {
2404            sdf::AttributeSpec::new(
2405                e.data_mut(),
2406                "/A.rel[/Target].attr",
2407                "double",
2408                sdf::Variability::Varying,
2409                false,
2410            )?;
2411            Ok(())
2412        }));
2413        // Well-formed variant selections are authorable (they build the variant
2414        // set / variant scaffolding), but malformed ones — here an empty
2415        // selection — must still be rejected.
2416        assert!(invalid(&mut layer, |e| {
2417            sdf::PrimSpec::new(e.data_mut(), "/A{x=}child", sdf::Specifier::Def, "Xform")?;
2418            Ok(())
2419        }));
2420    }
2421
2422    /// Authoring a prim inside a variant builds the full variant set / variant
2423    /// scaffolding with correct spec types and child registration.
2424    #[test]
2425    fn variant_authoring() -> Result<()> {
2426        let mut layer = sdf::Layer::new_anonymous("variants.usda");
2427        edit_layer(&mut layer, |e| {
2428            sdf::PrimSpec::new(e.data_mut(), "/Prim", sdf::Specifier::Def, "Xform").unwrap();
2429            sdf::PrimSpec::new(e.data_mut(), "/Prim{set=sel}child", sdf::Specifier::Def, "Scope").unwrap();
2430        });
2431
2432        assert_eq!(layer.data().spec_type(&sdf::path("/Prim")?), Some(sdf::SpecType::Prim));
2433        assert_eq!(
2434            layer.data().spec_type(&sdf::path("/Prim{set=}")?),
2435            Some(sdf::SpecType::VariantSet)
2436        );
2437        assert_eq!(
2438            layer.data().spec_type(&sdf::path("/Prim{set=sel}")?),
2439            Some(sdf::SpecType::Variant)
2440        );
2441        assert_eq!(
2442            layer.data().spec_type(&sdf::path("/Prim{set=sel}child")?),
2443            Some(sdf::SpecType::Prim)
2444        );
2445
2446        let token_vec = |path: &str, key: sdf::ChildrenKey| -> Result<Vec<String>> {
2447            match layer.data().get_field(&sdf::path(path)?, key.as_str())?.into_owned() {
2448                sdf::Value::TokenVec(v) => Ok(v.into_iter().map(Into::into).collect()),
2449                other => panic!("expected TokenVec at {path}, got {other:?}"),
2450            }
2451        };
2452        assert_eq!(token_vec("/Prim", sdf::ChildrenKey::VariantSetChildren)?, vec!["set"]);
2453        assert_eq!(
2454            token_vec("/Prim{set=}", sdf::ChildrenKey::VariantChildren)?,
2455            vec!["sel"]
2456        );
2457        assert_eq!(
2458            token_vec("/Prim{set=sel}", sdf::ChildrenKey::PrimChildren)?,
2459            vec!["child"]
2460        );
2461        Ok(())
2462    }
2463
2464    /// A bare variant-selection leaf isn't a prim, so prim authoring rejects it
2465    /// (rather than panicking on the `PrimSpecMut` downcast or writing prim
2466    /// fields onto the variant spec).
2467    #[test]
2468    fn prim_authoring_rejects_variant_leaf() -> Result<()> {
2469        let mut layer = sdf::Layer::new_anonymous("variants.usda");
2470        edit_layer(&mut layer, |e| {
2471            sdf::PrimSpec::new(e.data_mut(), "/Prim", sdf::Specifier::Def, "Xform").unwrap();
2472        });
2473        assert!(matches!(
2474            layer
2475                .edit(|e| {
2476                    sdf::PrimSpec::new(e.data_mut(), "/Prim{set=sel}", sdf::Specifier::Def, "Xform")?;
2477                    Ok(())
2478                })
2479                .unwrap_err(),
2480            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2481        ));
2482        assert!(matches!(
2483            layer
2484                .edit(|e| {
2485                    sdf::PrimSpec::over(e.data_mut(), "/Prim{set=sel}")?;
2486                    Ok(())
2487                })
2488                .unwrap_err(),
2489            sdf::EditError::Author(sdf::AuthoringError::InvalidPath { .. })
2490        ));
2491        // The rejected calls must not have authored a variant spec.
2492        assert_eq!(layer.data().spec_type(&sdf::path("/Prim{set=sel}")?), None);
2493        Ok(())
2494    }
2495
2496    #[test]
2497    fn usda_roundtrip() -> Result<()> {
2498        let mut layer = sdf::Layer::new_anonymous("scene.usda");
2499        let material = sdf::path("/World/Material")?;
2500        edit_layer(&mut layer, |e| {
2501            e.pseudo_root_mut().unwrap().set_default_prim("World");
2502            sdf::PrimSpec::new(e.data_mut(), "/World", sdf::Specifier::Def, "Xform").unwrap();
2503            let mut sphere = sdf::PrimSpec::new(e.data_mut(), "/World/Sphere", sdf::Specifier::Def, "Sphere").unwrap();
2504            sphere.set_kind("component");
2505            let mut radius = sdf::AttributeSpec::new(
2506                e.data_mut(),
2507                "/World/Sphere.radius",
2508                "double",
2509                sdf::Variability::Varying,
2510                false,
2511            )
2512            .unwrap();
2513            radius
2514                .set_default(sdf::Value::Double(1.5))
2515                .expect("value fits the declared type");
2516            sdf::PrimSpec::new(e.data_mut(), &material, sdf::Specifier::Def, "Material").unwrap();
2517            let mut binding = sdf::RelationshipSpec::new(
2518                e.data_mut(),
2519                "/World/Sphere.material:binding",
2520                sdf::Variability::Varying,
2521                false,
2522            )
2523            .unwrap();
2524            binding.add_target(material.clone()).unwrap();
2525            let mut surface = sdf::AttributeSpec::new(
2526                e.data_mut(),
2527                "/World/Sphere.inputs:surface",
2528                "token",
2529                sdf::Variability::Varying,
2530                false,
2531            )
2532            .unwrap();
2533            surface
2534                .set_connection_paths([sdf::path("/World/Material.outputs:surface").unwrap()])
2535                .unwrap();
2536        });
2537
2538        let tmp = std::env::temp_dir().join("openusd_authoring_roundtrip.usda");
2539        layer.export(tmp.to_str().unwrap())?;
2540
2541        let parsed = usda::read_file(&tmp)?;
2542        assert_eq!(parsed.spec_type(&sdf::path("/World")?), Some(sdf::SpecType::Prim));
2543        assert_eq!(
2544            parsed.spec_type(&sdf::path("/World/Sphere")?),
2545            Some(sdf::SpecType::Prim)
2546        );
2547        assert_eq!(
2548            parsed.spec_type(&sdf::path("/World/Sphere.radius")?),
2549            Some(sdf::SpecType::Attribute)
2550        );
2551        assert_eq!(
2552            parsed
2553                .get_field(&sdf::Path::abs_root(), sdf::FieldKey::DefaultPrim.as_str())?
2554                .into_owned(),
2555            sdf::Value::Token("World".into())
2556        );
2557        let targets = parsed
2558            .get_field(
2559                &sdf::path("/World/Sphere.material:binding")?,
2560                sdf::FieldKey::TargetPaths.as_str(),
2561            )?
2562            .into_owned()
2563            .try_as_path_list_op()
2564            .expect("relationship targets as PathListOp");
2565        assert!(targets.explicit);
2566        assert_eq!(targets.explicit_items, vec![material]);
2567        let connections = parsed
2568            .get_field(
2569                &sdf::path("/World/Sphere.inputs:surface")?,
2570                sdf::FieldKey::ConnectionPaths.as_str(),
2571            )?
2572            .into_owned()
2573            .try_as_path_list_op()
2574            .expect("connection paths as PathListOp");
2575        assert!(connections.explicit);
2576        assert_eq!(
2577            connections.explicit_items,
2578            vec![sdf::path("/World/Material.outputs:surface")?]
2579        );
2580        let _ = std::fs::remove_file(&tmp);
2581        Ok(())
2582    }
2583
2584    /// Puts `layer` behind a resolver that opens every asset to what `open`
2585    /// gives, as if the layer had been read through it.
2586    fn opened_through(layer: &mut Layer, open: impl Fn() -> io::Result<Box<dyn ar::Asset>> + 'static) {
2587        layer.resolver = Some(Rc::new(ar::tests::TestResolver(open)));
2588    }
2589
2590    /// The crate fixture in `dir`, read into memory and edited, with its
2591    /// path.
2592    fn edited_fixture(dir: &FsPath) -> Result<(PathBuf, Layer)> {
2593        let path = crate_fixture(dir)?;
2594        let mut layer = Layer::open(path.to_string_lossy())?;
2595        set_component(&mut layer);
2596        Ok((path, layer))
2597    }
2598
2599    /// An asset over `bytes` that shares them through a source standing in
2600    /// for a mapping when `mapping` is set, and for bytes in memory
2601    /// otherwise. `sources` collects a handle that is shared with each
2602    /// source made; its count tells whether a view of the source lives.
2603    fn shared_asset(bytes: Vec<u8>, mapping: bool, sources: &RefCell<Vec<ar::tests::AdviceLog>>) -> Box<dyn ar::Asset> {
2604        let (buffer, log) = ar::tests::recording_source(bytes, mapping);
2605        sources.borrow_mut().push(log);
2606        Box::new(io::Cursor::new(ar::AssetBuffer::Shared(buffer)))
2607    }
2608
2609    /// How many of `sources` a view still holds alive.
2610    fn live_sources(sources: &RefCell<Vec<ar::tests::AdviceLog>>) -> usize {
2611        sources.borrow().iter().filter(|log| Arc::strong_count(log) > 1).count()
2612    }
2613
2614    /// A save through the registry puts the layer on the mapping the
2615    /// resolver opens of the saved file, and the layer decodes from it.
2616    #[test]
2617    fn save_remaps() -> Result<()> {
2618        let dir = tempfile::tempdir()?;
2619        let (path, mut layer) = edited_fixture(dir.path())?;
2620        let sources = Rc::new(RefCell::new(Vec::new()));
2621        opened_through(&mut layer, {
2622            let (path, sources) = (path.clone(), Rc::clone(&sources));
2623            move || Ok(shared_asset(fs::read(&path)?, true, &sources))
2624        });
2625
2626        layer.save()?;
2627        assert_eq!(sources.borrow().len(), 1, "the saved file is opened once");
2628        assert_eq!(live_sources(&sources), 1, "the layer holds the mapping");
2629        assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2630        let size = layer.data().get_field(&Path::new("/World.size")?, "default")?;
2631        assert_eq!(size.into_owned(), Value::from(vec![1.0_f32; 64]));
2632        Ok(())
2633    }
2634
2635    /// A resolver that answers with a mapping of the file as it was before
2636    /// the save leaves the layer on the bytes it wrote: the saved values
2637    /// stay, and nothing reports a change.
2638    #[test]
2639    fn save_refuses_stale() -> Result<()> {
2640        let dir = tempfile::tempdir()?;
2641        let (path, mut layer) = edited_fixture(dir.path())?;
2642        let fired = Rc::new(Cell::new(0));
2643        layer.add_sink({
2644            let fired = Rc::clone(&fired);
2645            move |_: &str, _: &ChangeList| fired.set(fired.get() + 1)
2646        });
2647        let before = fs::read(&path)?;
2648        let sources = Rc::new(RefCell::new(Vec::new()));
2649        opened_through(&mut layer, {
2650            let sources = Rc::clone(&sources);
2651            move || Ok(shared_asset(before.clone(), true, &sources))
2652        });
2653
2654        layer.save()?;
2655        assert_eq!(sources.borrow().len(), 1);
2656        assert_eq!(live_sources(&sources), 0, "the stale mapping is let go");
2657        assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2658        assert_eq!(fired.get(), 0, "a save is not a layer change");
2659        assert_eq!(
2660            world_kind(&Layer::open(path.to_string_lossy())?),
2661            Some(tf::Token::from("component"))
2662        );
2663        Ok(())
2664    }
2665
2666    /// Shared bytes that are held in memory are not adopted, though they
2667    /// equal the file: the layer already holds those bytes.
2668    #[test]
2669    fn save_skips_heap() -> Result<()> {
2670        let dir = tempfile::tempdir()?;
2671        let (path, mut layer) = edited_fixture(dir.path())?;
2672        let sources = Rc::new(RefCell::new(Vec::new()));
2673        opened_through(&mut layer, {
2674            let sources = Rc::clone(&sources);
2675            move || Ok(shared_asset(fs::read(&path)?, false, &sources))
2676        });
2677
2678        layer.save()?;
2679        assert_eq!(sources.borrow().len(), 1);
2680        assert_eq!(live_sources(&sources), 0);
2681        assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2682        Ok(())
2683    }
2684
2685    /// An asset that shares no bytes is opened and dropped without a byte
2686    /// being read from it.
2687    #[test]
2688    fn save_reads_nothing() -> Result<()> {
2689        /// An asset that counts the reads made of it.
2690        struct Counting(io::Cursor<Vec<u8>>, Arc<AtomicUsize>);
2691
2692        impl io::Read for Counting {
2693            fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2694                self.1.fetch_add(1, Ordering::Relaxed);
2695                self.0.read(buffer)
2696            }
2697        }
2698
2699        impl io::Seek for Counting {
2700            fn seek(&mut self, position: io::SeekFrom) -> io::Result<u64> {
2701                self.0.seek(position)
2702            }
2703        }
2704
2705        impl ar::Asset for Counting {
2706            fn size(&self) -> io::Result<u64> {
2707                Ok(self.0.get_ref().len() as u64)
2708            }
2709        }
2710
2711        let dir = tempfile::tempdir()?;
2712        let (path, mut layer) = edited_fixture(dir.path())?;
2713        let (opens, reads) = (Rc::new(Cell::new(0)), Arc::new(AtomicUsize::new(0)));
2714        opened_through(&mut layer, {
2715            let (opens, reads) = (Rc::clone(&opens), Arc::clone(&reads));
2716            move || {
2717                opens.set(opens.get() + 1);
2718                Ok(Box::new(Counting(
2719                    io::Cursor::new(fs::read(&path)?),
2720                    Arc::clone(&reads),
2721                )))
2722            }
2723        });
2724
2725        layer.save()?;
2726        assert_eq!(opens.get(), 1);
2727        assert_eq!(reads.load(Ordering::Relaxed), 0);
2728        assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2729        Ok(())
2730    }
2731
2732    /// A saved file the resolver cannot open afterwards is still a save: the
2733    /// layer stays on the bytes it wrote.
2734    #[test]
2735    fn save_survives_reopen() -> Result<()> {
2736        let dir = tempfile::tempdir()?;
2737        let (path, mut layer) = edited_fixture(dir.path())?;
2738        opened_through(&mut layer, || Err(io::Error::from(io::ErrorKind::PermissionDenied)));
2739
2740        layer.save()?;
2741        let component = Some(tf::Token::from("component"));
2742        assert_eq!(world_kind(&layer), component);
2743        assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
2744        Ok(())
2745    }
2746
2747    /// A replacement that fails through the registry keeps the edit in
2748    /// memory as a plain save does, and asks the resolver for nothing.
2749    #[test]
2750    fn save_replace_failure() -> Result<()> {
2751        let dir = tempfile::tempdir()?;
2752        let (path, mut layer) = edited_fixture(dir.path())?;
2753        let opens = Rc::new(Cell::new(0));
2754        opened_through(&mut layer, {
2755            let opens = Rc::clone(&opens);
2756            move || {
2757                opens.set(opens.get() + 1);
2758                Err(io::Error::from(io::ErrorKind::NotFound))
2759            }
2760        });
2761        fs::remove_file(&path)?;
2762        fs::create_dir(&path)?;
2763
2764        let error = layer.save().expect_err("the replacement fails");
2765        assert!(matches!(error, ExportError::Replace { .. }), "{error:?}");
2766        assert_eq!(opens.get(), 0);
2767        assert_eq!(world_kind(&layer), Some(tf::Token::from("component")));
2768        Ok(())
2769    }
2770
2771    /// Through a resolver that maps files, the saved layer reads the new
2772    /// file through a mapping, a layer that viewed the old file goes on
2773    /// reading it, and a fresh open sees the edit.
2774    #[cfg(feature = "mmap")]
2775    #[test]
2776    // The mapping opt-in is `unsafe` by contract, and this test, the only
2777    // writer of its directory, can make the promise.
2778    #[allow(unsafe_code)]
2779    fn save_mapped() -> Result<()> {
2780        let dir = tempfile::tempdir()?;
2781        let path = crate_fixture(dir.path())?;
2782        // SAFETY: only the save below writes the files in this directory, and
2783        // a save never writes into a file it holds a view of: it replaces the
2784        // file beside it.
2785        let resolver = unsafe { ar::DefaultResolver::new().map_files() };
2786        let registry = sdf::LayerRegistry::new(Box::new(resolver));
2787        let identifier = registry.create_identifier(&path.to_string_lossy(), None);
2788        let mut editing = registry.open_layer(&identifier)?.expect("the layer resolves");
2789        let other = registry.open_layer(&identifier)?.expect("the layer resolves");
2790        set_component(&mut editing);
2791
2792        editing.save()?;
2793        let component = Some(tf::Token::from("component"));
2794        assert_eq!(world_kind(&editing), component);
2795        assert_eq!(world_kind(&other), None, "the other view reads the file it mapped");
2796        assert_eq!(world_kind(&Layer::open(path.to_string_lossy())?), component);
2797        let size = editing.data().get_field(&Path::new("/World.size")?, "default")?;
2798        assert_eq!(size.into_owned(), Value::from(vec![1.0_f32; 64]));
2799
2800        // A mapped file is open with write sharing denied. The new file
2801        // refuses a writer for as long as the saved layer views it.
2802        #[cfg(windows)]
2803        {
2804            let write = || fs::OpenOptions::new().write(true).open(&path);
2805            assert!(write().is_err(), "the saved layer maps its file");
2806            drop(editing);
2807            assert!(write().is_ok(), "the mapping went with the layer");
2808        }
2809        Ok(())
2810    }
2811}