Add ObjectRelation & Task Registries#736
Conversation
There was a problem hiding this comment.
Code Review: Add ObjectRelation Registry
Summary
This PR introduces a registry pattern for RelationBase subclasses via a new ObjectRelationLibraryRegistry and @register_object_relation decorator. The implementation follows established patterns in the codebase and provides a clean mechanism for dynamically resolving spatial constraint types to their corresponding relation classes.
✅ Strengths
-
Consistent Design Pattern: The new
ObjectRelationLibraryRegistryfollows the same singleton pattern as existing registries (DeviceRegistry,EnvironmentRegistry, etc.), maintaining codebase consistency. -
Clean Decorator API: The
@register_object_relationdecorator provides an intuitive way to register classes, mirroring the existing@register_environmentpattern. -
Good Test Coverage: The test additions verify both the registry lookup and the expected
Nonereturns for unregistered constraint types (AT_POSE,IN). -
Proper Lazy Loading: Integration with
ensure_assets_registered()ensures relations are loaded when needed, avoiding circular import issues.
⚠️ Suggestions & Concerns
1. Silent Warning on Duplicate Registration (Medium Priority)
def register_object_relation(cls):
registry = ObjectRelationLibraryRegistry()
if registry.is_registered(cls.name):
print(f"WARNING: Object relation {cls.name} is already registered. Doing nothing.")
else:
registry.register(cls, cls.name)
return clsIssue: Using print() for warnings can be easily missed and does not follow Python logging conventions.
Suggestion: Consider using warnings.warn() or the logging module for consistency with production code:
import warnings
warnings.warn(f"Object relation {cls.name} is already registered. Skipping.", UserWarning, stacklevel=2)This mirrors the pattern in other Isaac Lab modules and enables proper warning filtering.
2. Missing name Attribute Validation (Medium Priority)
The decorator assumes cls.name exists. If a class is decorated without defining name, this will raise an AttributeError with a potentially confusing traceback.
Suggestion: Add explicit validation:
def register_object_relation(cls):
if not hasattr(cls, 'name'):
raise TypeError(f"Cannot register {cls.__name__}: missing required 'name' class attribute")
# ... rest of implementation3. IsAnchor Class Body Change (Minor)
class IsAnchor(RelationBase):
# was: pass
name = "is_anchor"The removal of pass is correct since name now provides a class body, but ensure this does not break any introspection or serialization logic that may have relied on IsAnchor having no attributes.
4. Type Hint Return Consistency (Minor)
In relation_class_for_spatial_constraint_type:
) -> "type[RelationBase] | None":The string-quoted forward reference is fine, but consider importing RelationBase at runtime (guarded by TYPE_CHECKING) to keep the return type consistent with other functions in the module.
🔍 Edge Cases & Error Handling
-
Thread Safety: The singleton registry pattern uses a class-level
_instancesdict. If Isaac Lab ever runs with multi-threaded asset registration, this could cause race conditions. Not a concern for current usage patterns, but worth noting. -
Registry State Persistence: The registry is populated via imports in
ensure_assets_registered(). If modules are reloaded (e.g., during development/testing), the_assets_registeredflag may not reflect reality. This is a pre-existing pattern in the codebase, not introduced by this PR.
📋 Test Coverage Assessment
The added tests appropriately cover:
- ✅ Registry lookup for registered types (
IsAnchor,PositionLimits) - ✅
Nonereturn for unregistered types (AT_POSE,IN) - ✅ Integration with the existing
ArenaEnvGraphSpectest case
Consider adding (in a follow-up PR):
- Unit test for duplicate registration warning behavior
- Unit test for the decorator on a class missing the
nameattribute
📌 Verdict
Approve with suggestions. This is a well-structured addition that follows established patterns and includes appropriate tests. The suggestions above are improvements rather than blockers.
The TODO comment for AT_POSE and IN support is appropriately documented for future work.
🤖 Automated review by Isaac Lab Review Bot | Guidelines
Update (6a83dbe): New commits add lightwheel_utils.py with acquire_lightwheel_asset() for scoped timeout and retry handling of Lightwheel asset downloads. This is a good addition that addresses download reliability concerns. The implementation is clean with proper timeout restoration in the finally block and the _looks_like_timeout() helper correctly traverses exception chains. Test coverage is adequate. No new issues found — previous suggestions remain valid but are non-blocking. ✅
Update (70ff4c2): Significant expansion — this commit adds a TaskRegistry alongside the existing ObjectRelationLibraryRegistry, plus a @register_task decorator. All 14 concrete task classes (NoTask, PickAndPlaceTask, LiftObjectTask, LiftObjectTaskRL, DexsuiteLiftTask, GoalPoseTask, PressButtonTask, RotateRevoluteJointTask, CloseDoorTask, OpenDoorTask, TurnKnobTask, AssemblyTask, SortMultiObjectTask, PlaceUprightTask) are now decorated and auto-registered.
Key observations:
-
✅ Consistent pattern:
TaskRegistryfollows the same singleton approach as other registries. The@register_taskdecorator usescls.__name__as the key (appropriate since YAML configs usetype: PascalCase), unlike@register_object_relationwhich usescls.name. -
✅ Good test coverage:
test_task_registry.pyverifies all expected tasks can be resolved by name. -
✅ Proper lazy loading integration:
ObjectRelationLibraryRegistryandTaskRegistryare added to theisinstance()checks inis_registered(),get_component_by_name(), andget_all_keys()— ensuringensure_assets_registered()fires before lookups. -
⚠️ Sameprint()warning pattern: The@register_taskdecorator uses the sameprint(f"WARNING: ...")approach noted earlier. Consider usingwarnings.warn()for consistency. -
ℹ️ PR title outdated: The PR is titled "Add ObjectRelation Registry" but now also adds TaskRegistry. Consider updating the title/description to reflect the broader scope.
No blocking issues. The expansion is well-structured and follows established conventions. ✅
Update (820ae6d): Added re-entry protection to ensure_assets_registered() using _registration_in_progress flag. This prevents circular import issues that could occur when registration decorators call is_registered() mid-import (which would trigger another ensure_assets_registered() call before the first one completes). The fix is clean with proper try/finally to ensure the flag is cleared. No new issues. ✅
Update (5df5010): Code cleanup and refactoring. Key changes:
-
✅ Registry isinstance checks refactored: Replaced repeated
isinstance(self, (AssetRegistry, DeviceRegistry, RetargeterRegistry, PolicyRegistry, HDRImageRegistry))tuples with a singleREGISTRIESconstant. This is cleaner and ensures consistency when new registries are added. -
✅ REGISTRIES tuple excludes EnvironmentRegistry: The docstring correctly explains the exclusion — triggering
ensure_assets_registered()during environment registration causes an env↔tasks cycle. Good design decision. -
✅
tasks/__init__.pyexplicit imports: All concrete task modules are now explicitly imported in the package__init__.pyso decorators fire when the package is loaded. This ensures tasks are registered whenensure_assets_registered()importsisaaclab_arena.tasks. -
✅ Imports cleaned up:
ObjectRelationLibraryRegistryandTaskRegistryproperly added toregister.pyimports. -
ℹ️ Sequential tasks not registered:
sequential_task_baseis imported butSequentialTaskBasedoes not appear in the expected list intest_task_registry.py. This appears intentional — it's a base class, not a concrete task.
No new issues introduced. The refactoring improves maintainability. ✅
Greptile SummaryThis PR adds
Confidence Score: 5/5The change is safe to merge; the re-entrancy guard, lazy-loading refactor, and new registry classes are well-reasoned and covered by tests. The registration mechanism correctly threads ensure_loaded=False through all decorator call sites, the _registration_in_progress guard prevents the circular-import/segfault that motivated the PR, and the REGISTRIES tuple cleanly replaces the three hard-coded isinstance checks. The only finding is a dead import of sequential_task_base in task_library.py, which has no runtime impact. isaaclab_arena/tasks/task_library.py — the sequential_task_base import registers nothing and can be removed. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Caller: get_task_by_name / get_object_relation_by_name\nor is_registered(ensure_loaded=True)"] --> B{{"_assets_registered?"}}
B -- Yes --> Z["Return from registry"]
B -- No --> C{{"_registration_in_progress?"}}
C -- Yes --> D["Return early (re-entrant call)"]
C -- No --> E["Set _registration_in_progress = True"]
E --> F["Import library modules\n(relations.py, task_library.py, ...)"]
F --> G["@register_object_relation / @register_task\ndecorators fire during import"]
G --> H["is_registered(ensure_loaded=False)\n→ no re-entrancy, just checks _components"]
H -- Not duplicate --> I["registry.register(cls, cls.name)"]
H -- Duplicate --> J["print WARNING, skip"]
I --> K["Set _assets_registered = True\n(finally: _registration_in_progress = False)"]
K --> Z
Reviews (6): Last reviewed commit: "fix import" | Re-trigger Greptile |
4a2d620 to
6a83dbe
Compare
is_registered, get_component_by_name, and get_all_keys on the base Registry only lazy-load assets for the existing isinstance set (Asset/Device/Retargeter/Policy/HDRImage). Without ObjectRelationLibraryRegistry in that set, calling is_registered before relations.py is imported returned False for every name, so relation_class_for_spatial_constraint_type silently returned None even for fully-registered classes like IsAnchor / PositionLimits. Add the registry to all three isinstance guards. Signed-off-by: Xinjie Yao <xyao@nvidia.com>
alexmillane
left a comment
There was a problem hiding this comment.
Looks great. Thanks for fixing an long-standing issue while doing this!
A few small comments. One requested test addition. Merge when you think you've addressed those.
| AssetRegistry, | ||
| DeviceRegistry, | ||
| RetargeterRegistry, | ||
| PolicyRegistry, | ||
| HDRImageRegistry, | ||
| ObjectRelationLibraryRegistry, | ||
| TaskRegistry, |
There was a problem hiding this comment.
It's not your code (it's my bad), but shall we take this chance to clean this up and define
REGISTRIES = (
AssetRegistry,
DeviceRegistry,
RetargeterRegistry,
PolicyRegistry,
HDRImageRegistry,
ObjectRelationLibraryRegistry,
TaskRegistry,
)
then
if isinstance(self, REGISTRIES):
ensure_assets_registered()
| @@ -233,5 +306,9 @@ def ensure_assets_registered(): | |||
| import isaaclab_arena.assets.retargeter_library # noqa: F401 | |||
| import isaaclab_arena.embodiments # noqa: F401 | |||
| import isaaclab_arena.policy # noqa: F401 | |||
| import isaaclab_arena.relations.relations # noqa: F401 | |||
| import isaaclab_arena.tasks # noqa: F401 | |||
|
|
|||
| _assets_registered = True | |||
| finally: | |||
| _registration_in_progress = False | |||
There was a problem hiding this comment.
Nice. This issue has been hanging around since Arena day 1.
Thanks for fixing it.
| # Import concrete task modules so their @register_task decorators fire when the | ||
| # tasks package is loaded (e.g. via ensure_assets_registered()). | ||
| from isaaclab_arena.tasks import ( # noqa: F401 | ||
| assembly_task, | ||
| close_door_task, | ||
| goal_pose_task, | ||
| lift_object_task, | ||
| no_task, | ||
| open_door_task, | ||
| pick_and_place_task, | ||
| place_upright_task, | ||
| press_button_task, | ||
| rotate_revolute_joint_task, | ||
| sequential_task_base, | ||
| sorting_task, | ||
| turn_knob_task, | ||
| ) |
| cls = registry.get_task_by_name(name) | ||
| assert cls.__name__ == name, f"{name} -> {cls.__name__}" | ||
| return True | ||
|
|
There was a problem hiding this comment.
We have these two objects that mirror each other:
- subclasses of
RelationBase, that are registered with theRelationRegistry - members of the
ArenaEnvGraphSpatialConstraintTypeenum
Right now we have this constraint that all registered relations should have a corresponding enum item with the same name.
I feel it makes sense to test that that condition holds true over time. Could you add a test for that (obviously we'll have to exclude (manually) at_pose and in (for now)).
Summary
Add ObjectRelation Registry & Task Registry
Detailed description
ArenaEnvfromEnvGraphSpec, it requires matching spatial relationship & task to its class name. Without this registry, new relation/task need to be added in two places manually.@register_object_relationdecorator backed by the addedObjectRelationLibraryRegistry. Added@register_taskdecorator backed by the addedTaskRegistry.AT_POSEandINregistry supportsNote