Some clarity on ICleanupComponent

Good afternoon!
I was recently trying to implement a number of ICleanupComponent instances to help simplify queries for deleting entities from the world.

I ran into a scenario where one of the components I’d like to clean up is basically on the entity for it’s entire lifetime. It can be added during baking or attached at runtime. This is a struct that contains a BlobAssetReference, when the blob is created at runtime I’d like to dispose of the blob when the entity is destroyed.

Here is an example of a query I’m using. The type “HeightMap” needs to be cleaned up but if I change the definition of HeightMap to implement ICleanupComponent, and pass the query to state.RequireForUpdate(generateQuery), then OnUpdate is never called. Even when the component is definitely present on the entity.
HeightMap is a struct and considered “unmanaged”.

generateQuery = SystemAPI.QueryBuilder()
    .WithAll<MeshTerrain, MaterialMeshInfo, RenderBounds, HeightMap, GenerateMesh>()
    .WithNone<MeshResult>()
    .Build();

To add to my confusion, I have another component “MeshReference” that is a managed component that implements ICleanupComponent.

terrainsQuery = SystemAPI.QueryBuilder()
    .WithAll<MeshTerrain, MaterialMeshInfo, RenderBounds, LocalToWorld, MeshReference>()
    .WithPresent<IsShowingChildren>()
    .WithNone<GenerateMesh, GenerateChildren, GenerateCollider, QuadtreeEndNode>()
    .Build();

This query executes with no issues, even though there is an ICleanupComponent in the query, the only difference is this one is a managed class. It still behaves like any other ICleanupComponent though, in the sense that it’s still present on the entity after the entity is destroyed, so I can destroy the mesh.

I prefer how the managed object behaves in this instance because I can still use the data that the object contains while the entity is alive. If I convert HeightMap to an ICleanupComponent and leave it as an unmanaged struct then I wouldn’t be able to query for the data it contains until the entity is destroyed. This is a bit cumbersome and converting HeightMap to be a managed class so it functions similar to MeshReference is not an option as HeightMap is used in several bursted jobs it cannot be managed.

Is there some logical reason why unmanaged ICleanupComponents behave so differently from managed ones? How can I get them to behave more similarly without a bunch of hacks? I’d rather not copy the data into an ICleanupComponent on deletion if I can help it.

Only unmanaged components are cleanup components. The framework is likely ignoring the cleanup component interface implementation for the managed component type. Queries should accept cleanup components, and there’s no special treatment in EntityQueryOptions that’s specific to them.

Considering that you speak of baking, keep in mind that cleanup components can’t be baked. As you’re not getting the system running, look in the Entity Hierarchy to check if any entities have the necessary components for the query. Any baked entities and instantiations of them shouldn’t end up with cleanup components. Cleanup components would need to be added at runtime through some means, usually a system that’s dedicated to adding the cleanup component to entities needing / lacking it and possibly also responsible for cleaning the data up.

Hmm, it seems you’re correct about the baking. I must have overlooked that. I could have swore that I saw the component on the entity but I guess I was mistaken.

However, concerning the managed cleanup component, it would appear to honor the declaration. From usage I can verify that when the object is deleted the managed object still exists and I am able to destroy it along with the unmanaged ones I was creating at runtime. Whether this is due to the interface or something else I am not sure, I’ll leave it there for clarity. This object is attached to the entity at runtime, though, and seems to be the major difference between this and the HeightMap, which in this particular instance is baked.

The proper way to clean up data in a managed component is to implement IDisposable.

I will keep this in mind, since I actually prefer this method.
However, whether it is the proper way or not, the engine appears to treat them the same way regardless. I query for entities that have the cleanup components but not the MeshTerrain component, and the entities with only MeshReference components still exist in the query. I may have got there in a roundabout way but at least it wasn’t leaking meshes, which was the idea

What would you recommend to do with IDisposable if I also use EntitiesGraphicsSystem to register and unregister the meshes? I use these methods so I don’t have to interact directly with RenderMeshArray most of the time. When the entity is being cleaned up I access that system. To do that with IDisposable I would need the component to keep a reference to the system or query for it by other means inside Dispose() which is less preferable to just getting it from (Edit: SystemState)

Deregistration of a resource registered in an external context should be handled in a separate system. The keys are unmanaged, so they would be best suited for an unmanaged component.