Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 6 additions & 24 deletions motionplan/armplanning/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,29 +284,9 @@ func PlanMotion(ctx context.Context, parentLogger logging.Logger, request *PlanR
// fine — failures here are logged and swallowed rather than surfaced as a
// planning error.
func resetMeshCaches(ctx context.Context, logger logging.Logger, request *PlanRequest) {
visit := func(geometry spatialmath.Geometry) {
if mesh, ok := geometry.(*spatialmath.Mesh); ok {
mesh.ResetCache()
}
}

if request.StartState != nil && request.StartState.structuredConfiguration != nil {
frameSystemGeometries, err := referenceframe.FrameSystemGeometries(
request.FrameSystem,
request.StartState.structuredConfiguration,
)
if err != nil {
logger.CDebugf(ctx, "resetMeshCaches: skipping robot geometries: %v", err)

@dgottlieb dgottlieb Jun 2, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens when the start state is out of bounds. And we won't return related geometries in those cases.

So I was worried we weren't clearing the cache in some cases when it was useful. But I now concluded this is fine? Because I expect this only gets hit when we never filled up the mesh cache?

// Not expected to happen, but we still want to try walking the world state. Rely on
// ranging over a nil slice to be a no-op.
}

for _, geometriesInFrame := range frameSystemGeometries {
for _, geometry := range geometriesInFrame.Geometries() {
visit(geometry)
}
}
}
// The request frame system holds mesh geometries that are shared are expected to persist across an entire
// process lifetime.
request.FrameSystem.ResetCaches()

if request.WorldState != nil {
// Right now, the world state is getting entirely thrown away anyways. It's always a part of
Expand All @@ -322,7 +302,9 @@ func resetMeshCaches(ctx context.Context, logger logging.Logger, request *PlanRe
}

for _, geometry := range obstacles.Geometries() {
visit(geometry)
if mesh, ok := geometry.(*spatialmath.Mesh); ok {
mesh.ResetCache()
}
}
}
}
Expand Down
38 changes: 38 additions & 0 deletions referenceframe/frame_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,44 @@ func NewEmptyFrameSystem(name string) *FrameSystem {
}
}

// ResetCaches walks the frame system for all mesh geometries and invokes their `ResetCache`
// method. This is only required to avoid piling up too much memory usage with cache values are low
// value. Cached values are important in the context of a single motion plan request, but become
// increasingly irrelevant when degrees of freedom change.
func (sfs *FrameSystem) ResetCaches() {
resetMesh := func(geom spatial.Geometry) {
if mesh, isMesh := geom.(*spatial.Mesh); isMesh {
mesh.ResetCache()
}
}

var resetFrame func(Frame)
resetFrame = func(frameI Frame) {
switch frame := frameI.(type) {
case *staticFrame:
resetMesh(frame.geometry)
case *tailGeometryStaticFrame:
resetMesh(frame.geometry)
case *translationalFrame:
resetMesh(frame.geometry)
case *rotationalFrame:
// no geometries
case *namedFrame:
resetFrame(frameI)
case *poseFrame:
for _, geom := range frame.geometries {
resetMesh(geom)
}
case *SimpleModel:
frame.internalFS.ResetCaches()
}
}

for _, frameI := range sfs.frames {
resetFrame(frameI)
}
}

// resolveFrameInputs is a fallback for when linearInputs.Get(frameName) returns nil.
// It checks if frameName belongs to a flattened model and, if so, extracts the right
// slice from a component-name-keyed entry in the LinearInputs.
Expand Down
Loading