Skip to content

Commit

Permalink
Changing the strength ordering of auto-apply API schemas when applied
Browse files Browse the repository at this point in the history
to prim definitions so that it ensures later versions of an API schema
will always be stronger than earlier versions of the same schema if
multiple versions apply to the same type.
This ensures that the latest of the auto-applied versions will always be
the one applied when prim definitions are composed.
This does also change the strength order for auto-applied API schemas in
the general case which should be fine because the ordering itself is
arbitrary but consistent. Auto-apply API schemas are already expected to
be unable to rely on their relative strength order to each other and
should have their properties namespaced to avoid conflicts.

(Internal change: 2261246)
  • Loading branch information
pixar-oss committed Jan 31, 2023
1 parent 637fe9b commit 5b2d6b2
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 34 deletions.
53 changes: 37 additions & 16 deletions pxr/usd/usd/schemaRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,11 @@ struct _APISchemaApplyToInfoCache {

// Mapping of API schema type name to a list of type names it should auto
// applied to.
std::map<TfToken, TfTokenVector> autoApplyAPISchemasMap;
UsdSchemaRegistry::TokenToTokenVectorMap autoApplyAPISchemasMap;

// Mapping of API schema type name to a list of prim type names that it
// is ONLY allowed to be applied to.
TfHashMap<TfToken, TfTokenVector, TfHash> canOnlyApplyAPISchemasMap;
UsdSchemaRegistry::TokenToTokenVectorMap canOnlyApplyAPISchemasMap;

// Mapping of multiple apply API schema type name to a set of instance names
// that are the only allowed instance names for that type.
Expand Down Expand Up @@ -835,7 +835,7 @@ _GetNameListFromMetadata(const JsObject &dict, const TfToken &key)
/*static*/
void
UsdSchemaRegistry::CollectAddtionalAutoApplyAPISchemasFromPlugins(
std::map<TfToken, TfTokenVector> *autoApplyAPISchemas)
TokenToTokenVectorMap *autoApplyAPISchemas)
{
TRACE_FUNCTION();

Expand Down Expand Up @@ -942,23 +942,44 @@ _GetTypeToAutoAppliedAPISchemaNames()

// With all the apply to types collected we can add the API schema to
// the list of applied schemas for each Typed schema type.
//
// Note that the auto apply API schemas map is sorted alphabetically by
// API schema name so this list for each prim type will also be sorted
// alphabetically which is intentional. This ordering is arbitrary but
// necessary to ensure we get a consistent strength ordering for auto
// applied schemas every time. In practice, schema writers should be
// careful to make sure that auto applied API schemas have unique
// property names so that application order doesn't matter, but this at
// least gives us consistent behavior if property name collisions occur.
for (const TfType &applyToType : applyToTypes) {
result[applyToType].push_back(apiSchemaName);
}
}

// We have to sort the auto apply API schemas for each type here to be in
// reverse "dictionary order" for two reasons.
// 1. To ensure that if multiple versions of an API schema exist and
// auto-apply to the same schema, then the latest version of the API
// schema that is auto-applied will always be stronger than any of the
// earlier versions that are also auto-applied.
// 2. To enforce an arbitrary, but necessary, strength ordering for auto
// applied schemas that is consistent every time the schema registry is
// initialized. In practice, schema writers should be careful to make
// sure that auto applied API schemas have unique property names so that
// application order doesn't matter, but this at least gives us
// consistent behavior if property name collisions occur.
for (auto &typeAndAutoAppliedSchemas : result) {
Usd_SortAutoAppliedAPISchemas(&typeAndAutoAppliedSchemas.second);
}

return result;
}

void Usd_SortAutoAppliedAPISchemas(TfTokenVector *autoAppliedAPISchemas) {
if (autoAppliedAPISchemas->size() < 2) {
return;
}
// Sort in reverse dictionary order. This ensures that later versions of
// a schema will always appear before earlier versions of the same schema
// family if present in this list. Outside of this, the ordering is
// arbitrary.
std::sort(autoAppliedAPISchemas->begin(), autoAppliedAPISchemas->end(),
[](const TfToken &lhs, const TfToken &rhs) {
return TfDictionaryLessThan()(rhs, lhs);
});
}

// Helper class for initializing the schema registry by finding all generated
// schema types in plugin libraries and creating the static prim definitions
// for all concrete and applied API schema types.
Expand Down Expand Up @@ -1846,7 +1867,7 @@ UsdSchemaRegistry::GetTypeNameAndInstance(const TfToken &apiSchemaName)
}

/*static*/
const std::map<TfToken, TfTokenVector> &
const UsdSchemaRegistry::TokenToTokenVectorMap &
UsdSchemaRegistry::GetAutoApplyAPISchemas()
{
return _GetAPISchemaApplyToInfoCache().autoApplyAPISchemasMap;
Expand Down Expand Up @@ -1917,7 +1938,7 @@ const TfTokenVector &
UsdSchemaRegistry::GetAPISchemaCanOnlyApplyToTypeNames(
const TfToken &apiSchemaName, const TfToken &instanceName)
{
const TfHashMap<TfToken, TfTokenVector, TfHash> &canOnlyApplyToMap =
const TokenToTokenVectorMap &canOnlyApplyToMap =
_GetAPISchemaApplyToInfoCache().canOnlyApplyAPISchemasMap;

if (!instanceName.IsEmpty()) {
Expand Down Expand Up @@ -2072,8 +2093,8 @@ void
Usd_GetAPISchemaPluginApplyToInfoForType(
const TfType &apiSchemaType,
const TfToken &apiSchemaName,
std::map<TfToken, TfTokenVector> *autoApplyAPISchemasMap,
TfHashMap<TfToken, TfTokenVector, TfHash> *canOnlyApplyAPISchemasMap,
UsdSchemaRegistry::TokenToTokenVectorMap *autoApplyAPISchemasMap,
UsdSchemaRegistry::TokenToTokenVectorMap *canOnlyApplyAPISchemasMap,
TfHashMap<TfToken, TfToken::Set, TfHash> *allowedInstanceNamesMap)
{
PlugPluginPtr plugin =
Expand Down
17 changes: 13 additions & 4 deletions pxr/usd/usd/schemaRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ using UsdSchemaVersion = unsigned int;
///
class UsdSchemaRegistry : public TfWeakBase, boost::noncopyable {
public:
using TokenToTokenVectorMap =
std::unordered_map<TfToken, TfTokenVector, TfHash>;

/// Structure that holds the information about a schema that is registered
/// with the schema registry.
struct SchemaInfo {
Expand Down Expand Up @@ -403,7 +406,7 @@ class UsdSchemaRegistry : public TfWeakBase, boost::noncopyable {
/// include derived types of the listed types, the type lists returned by
/// this function do not.
USD_API
static const std::map<TfToken, TfTokenVector> &GetAutoApplyAPISchemas();
static const TokenToTokenVectorMap &GetAutoApplyAPISchemas();

/// Collects all the additional auto apply schemas that can be defined in
/// a plugin through "AutoApplyAPISchemas" metadata and adds the mappings
Expand All @@ -418,7 +421,7 @@ class UsdSchemaRegistry : public TfWeakBase, boost::noncopyable {
/// may want to collect just these plugin API schema mappings.
USD_API
static void CollectAddtionalAutoApplyAPISchemasFromPlugins(
std::map<TfToken, TfTokenVector> *autoApplyAPISchemas);
TokenToTokenVectorMap *autoApplyAPISchemas);

/// Creates a name template that can represent a property or API schema that
/// belongs to a multiple apply schema and will therefore have multiple
Expand Down Expand Up @@ -582,10 +585,16 @@ USD_API_TEMPLATE_CLASS(TfSingleton<UsdSchemaRegistry>);
void Usd_GetAPISchemaPluginApplyToInfoForType(
const TfType &apiSchemaType,
const TfToken &apiSchemaName,
std::map<TfToken, TfTokenVector> *autoApplyAPISchemasMap,
TfHashMap<TfToken, TfTokenVector, TfHash> *canOnlyApplyAPISchemasMap,
UsdSchemaRegistry::TokenToTokenVectorMap *autoApplyAPISchemasMap,
UsdSchemaRegistry::TokenToTokenVectorMap *canOnlyApplyAPISchemasMap,
TfHashMap<TfToken, TfToken::Set, TfHash> *allowedInstanceNamesMap);

// Utility for sorting a list of auto-applied API schemas. It is useful for
// certain clients to be able to make sure they can perform this type of sort
// in the exact same way as UsdSchemaRegistry does.
void Usd_SortAutoAppliedAPISchemas(
TfTokenVector *autoAppliedAPISchemas);

PXR_NAMESPACE_CLOSE_SCOPE

#endif //PXR_USD_USD_SCHEMA_REGISTRY_H
28 changes: 14 additions & 14 deletions pxr/usd/usd/testenv/testUsdAppliedAPISchemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,8 @@ def test_TypedPrimsOnStageWithAutoAppliedAPIs(self):
self.assertEqual(typedPrim.GetTypeName(), 'TestTypedSchemaForAutoApply')
self.assertEqual(typedPrim.GetAppliedSchemas(),
["TestMultiApplyAPI:builtin",
"TestMultiApplyAPI:autoFoo",
"TestSingleApplyAPI"])
"TestSingleApplyAPI",
"TestMultiApplyAPI:autoFoo"])
self.assertEqual(typedPrim.GetPrimTypeInfo().GetTypeName(),
'TestTypedSchemaForAutoApply')
# Note that prim type info does NOT contain the applied API
Expand Down Expand Up @@ -2259,12 +2259,12 @@ def test_APISchemasAutoAppliedToAPISchemas(self):
"TestAutoAppliedToAPI",
# Built-in API of 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:builtin",
# Defined in plugin metadata that 'TestMultiApplyAPI:autoFoo' auto
# applies to 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:autoFoo",
# 'TestSingleApplyAPI' defines in its schema def that it auto
# applies to 'TestAutoAppliedToAPI'
"TestSingleApplyAPI"])
"TestSingleApplyAPI",
# Defined in plugin metadata that 'TestMultiApplyAPI:autoFoo' auto
# applies to 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:autoFoo"])
self.assertTrue(prim.HasAPI(self.AutoAppliedToAPIType))
self.assertTrue(prim.HasAPI(self.MultiApplyAPIType, "builtin"))
self.assertTrue(prim.HasAPI(self.MultiApplyAPIType, "autoFoo"))
Expand Down Expand Up @@ -2308,12 +2308,12 @@ def test_APISchemasAutoAppliedToAPISchemas(self):
"TestAutoAppliedToAPI",
# Built-in API of 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:builtin",
# Defined in plugin metadata that 'TestMultiApplyAPI:autoFoo' auto
# applies to 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:autoFoo",
# 'TestSingleApplyAPI' defines in its schema def that it auto
# applies to 'TestAutoAppliedToAPI'
"TestSingleApplyAPI"])
"TestSingleApplyAPI",
# Defined in plugin metadata that 'TestMultiApplyAPI:autoFoo' auto
# applies to 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:autoFoo"])
self.assertTrue(prim.HasAPI(self.NestedAutoAppliedToAPIType))
self.assertTrue(prim.HasAPI(self.AutoAppliedToAPIType))
self.assertTrue(prim.HasAPI(self.MultiApplyAPIType, "foo"))
Expand Down Expand Up @@ -2363,12 +2363,12 @@ def test_APISchemasAutoAppliedToAPISchemas(self):
"TestAutoAppliedToAPI",
# Built-in API of 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:builtin",
# Defined in plugin metadata that 'TestMultiApplyAPI:autoFoo' auto
# applies to 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:autoFoo",
# 'TestSingleApplyAPI' defines in its schema def that it auto
# applies to 'TestAutoAppliedToAPI'
"TestSingleApplyAPI"])
"TestSingleApplyAPI",
# Defined in plugin metadata that 'TestMultiApplyAPI:autoFoo' auto
# applies to 'TestAutoAppliedToAPI'
"TestMultiApplyAPI:autoFoo"])

# Prim's authored applied API schemas is empty as the API schemas are
# part of the type (through auto apply).
Expand Down
64 changes: 64 additions & 0 deletions pxr/usd/usd/testenv/testUsdSchemaVersioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -1877,5 +1877,69 @@ def _MakeNewPrimPath() :
["multi:bar:foo:m_attr2",
"multi:bar:m_attr1"])

def test_AutoApplyAPI(self):
"""Tests versioning behavior with auto-apply API schemas"""

# Verify the auto-apply API schemas setup for this test.
# We have three versions of the same schema family:
# Version 0 - TestVersionedAutoApplyAPI
# Version 2 - TestVersionedAutoApplyAPI_2
# Version 10 - TestVersionedAutoApplyAPI_10
#
# These versions were chosen as the version order, 10 > 2 > 0, is
# different than the lexicographical order of the suffixes,
# "_2" > "_10" > "".
#
# All three versions of the schema are set up to auto apply to
# version 1 of the BasicVersioned typed schema. Only version 0
# of the auto-apply schema is setup to auto-apply to version 0
# of the BasicVersioned typed schema.
autoApplySchemas = Usd.SchemaRegistry.GetAutoApplyAPISchemas()
self.assertEqual(autoApplySchemas["TestVersionedAutoApplyAPI"],
["TestBasicVersioned", "TestBasicVersioned_1"])
self.assertEqual(autoApplySchemas["TestVersionedAutoApplyAPI_2"],
["TestBasicVersioned_1"])
self.assertEqual(autoApplySchemas["TestVersionedAutoApplyAPI_10"],
["TestBasicVersioned_1"])

# Create a prim typed with each version of the basic IsA schema.
stage = Usd.Stage.CreateInMemory()
primV0 = stage.DefinePrim("/Prim", "TestBasicVersioned")
primV1 = stage.DefinePrim("/Prim1", "TestBasicVersioned_1")
primV2 = stage.DefinePrim("/Prim2", "TestBasicVersioned_2")

self.assertTrue(primV0)
self.assertTrue(primV1)
self.assertTrue(primV2)

self.assertTrue(primV0.IsA(self.BasicVersion0Type))
self.assertTrue(primV1.IsA(self.BasicVersion1Type))
self.assertTrue(primV2.IsA(self.BasicVersion2Type))

# TestBasicVersioned has only version 0 of TestVersionedAutoApplyAPI
# auto-applied, so its the only API schema that applied to it.
self.assertEqual(primV0.GetAppliedSchemas(),
["TestVersionedAutoApplyAPI"])
self.assertEqual(primV0.GetPropertyNames(),
["a_attr"])

# TestBasicVersioned_1 has all three versions of
# TestVersionedAutoApplyAPI applied. In this case only the latest
# version of the schema family is applied which is version 10.
self.assertEqual(primV1.GetAppliedSchemas(),
["TestVersionedAutoApplyAPI_10"])
self.assertEqual(primV1.GetPropertyNames(),
["a_attr10"])

# TestBasicVersioned_2 has none of the versions of
# TestVersionedAutoApplyAPI explicitly applied. This case proves that
# the auto-applied schemas of the earlier versions of TestBasicVersioned
# do not automatically propagate to this later version.
self.assertEqual(primV2.GetAppliedSchemas(),
[])
self.assertEqual(primV2.GetPropertyNames(),
[])


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,18 @@ class "TestMultiApplyWithAPIVersionConflict_API" (
{
}

class "TestVersionedAutoApplyAPI"
{
int a_attr
}

class "TestVersionedAutoApplyAPI_2"
{
int a_attr2
}

class "TestVersionedAutoApplyAPI_10"
{
int a_attr10
}

Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,46 @@
],
"schemaKind": "singleApplyAPI"
},
"TestUsdSchemaVersioningTestVersionedAutoApplyAPI": {
"alias": {
"UsdSchemaBase": "TestVersionedAutoApplyAPI"
},
"apiSchemaAutoApplyTo": [
"TestBasicVersioned",
"TestBasicVersioned_1"
],
"autoGenerated": true,
"bases": [
"UsdAPISchemaBase"
],
"schemaKind": "singleApplyAPI"
},
"TestUsdSchemaVersioningTestVersionedAutoApplyAPI_10": {
"alias": {
"UsdSchemaBase": "TestVersionedAutoApplyAPI_10"
},
"apiSchemaAutoApplyTo": [
"TestBasicVersioned_1"
],
"autoGenerated": true,
"bases": [
"UsdAPISchemaBase"
],
"schemaKind": "singleApplyAPI"
},
"TestUsdSchemaVersioningTestVersionedAutoApplyAPI_2": {
"alias": {
"UsdSchemaBase": "TestVersionedAutoApplyAPI_2"
},
"apiSchemaAutoApplyTo": [
"TestBasicVersioned_1"
],
"autoGenerated": true,
"bases": [
"UsdAPISchemaBase"
],
"schemaKind": "singleApplyAPI"
},
"TestUsdSchemaVersioningTestVersionedMultiApplyAPI": {
"alias": {
"UsdSchemaBase": "TestVersionedMultiApplyAPI"
Expand Down
30 changes: 30 additions & 0 deletions pxr/usd/usd/testenv/testUsdSchemaVersioning/resources/schema.usda
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,33 @@ class "TestMultiApplyWithAPIVersionConflict_API" (
)
{
}

class "TestVersionedAutoApplyAPI" (
inherits = </APISchemaBase>
customData = {
token[] apiSchemaAutoApplyTo = ["TestBasicVersioned", "TestBasicVersioned_1"]
}
)
{
int a_attr
}

class "TestVersionedAutoApplyAPI_2" (
inherits = </APISchemaBase>
customData = {
token[] apiSchemaAutoApplyTo = ["TestBasicVersioned_1"]
}
)
{
int a_attr2
}

class "TestVersionedAutoApplyAPI_10" (
inherits = </APISchemaBase>
customData = {
token[] apiSchemaAutoApplyTo = ["TestBasicVersioned_1"]
}
)
{
int a_attr10
}

0 comments on commit 5b2d6b2

Please sign in to comment.