I want to write a Smart Pointer that tracks an entity on drop and modifiation. This can be useful to track what changes made to an entity needs to be persisted. I have a working solution for the modification part.
The drop part is much harder.
What I came up with so far is an enum very similar to std::borrow::Cow with 2 variants Attached and Detached.
Both variants have a Rc<RefCell<E>> that contains the entity. The Attached variant also contains a tracker for that type.
The inner struct of the Attached variant calls the tracker on drop.
I need the lifetimes on it, to ensure that an entity can only outlive it's tracker if it get's detached Additionally the it should implement a "Clone on Detach" behavior, so that the tracker cannot see modifications made to the entity after detach. That's important since the tracker has access to the Rc inside the Smart Pointer and could have made a clone of it.
When an tracker is attached it should recursively attach to all subentities.
But this can't work with my current design since the Tracker for both types would be different.
A solution could be to use an enum and implement From for Enum and use that to track multiple different kinds of entities, but this lead to very excessive bounds when i attempted using that approach.
Another solution could be something like that visitor pattern, but since this is intended as a library and I don't know the user types, I don't know how i would define the visitor trait since that would need a visit_entity method for each entity type, that i can't know ahead of time.
Solutions with TypeId also don't seem to work since it either leads to that the Tracker trait is not object-safe or that I can't get a valid TypeId since my Entities have non-static lifetimes.
I'm kinda stuck on the design of the interface. I feel like the implementation shouldn't be too hard after that is solved.
Help on defining a better interface is very much appreciated.
I would assume that if you want to control all access to the entity, your smart pointer would be the only owner of the entity. So I am not sure why the variants hold an Rc<RefCell<E>> type. This way clones of the rc could access it without going through the tracker?
Could you maybe write a version of your ideal API? How woud you like it to look ideally?
This is my current version, a liitle bit simplified to focus on the essential parts.
There are 2 places where tracking takes place. One tracks the dropping of the entity and the other tracks modifications (and maybe reads for optimistic locking).
I want to use this datastructure for entities loaded from db. When i load entities from db I will add them to an entity map/ 1st level cache and are registered initially with the tracker.
They are stored in an Arc and I use the atomic_refcell library for interior mutability (I use 1 enum for the key and 1 enum for the entities to have a single map for all different entity types).
I use Arc instead of Rc, since i want to use the entities with tokio.
Once I've run all my business logic, a commit will be called on the tracker (unit of work pattern) and all necessary changes will be written to the db, the tracker and entity map will be dropped on success.
To detect entities that are removed from collections i need to track the dropping of those entities.
Since I cannot move out of an entity in a destructor i also use clone on the arc to keep the struct alive.
I've considered Manually Drop for this before, but because all entities will needs to be accessible in the cache I've opted for Rc.
I actually start to like this approach more and more, the only downside is that I cannot guarantee with this approach that when I call commit no other thread can modify the entities while I'm commiting.
If I could associated the Struct with the lifetime of the tracker or the map I could guarantee that when I commit, no other thread can have access to my entities since I drop the tracker and the map at the end of the commit and therefore no references to that data can exist anymore.
But I need to be able to explicitly detach data from the tracker/map to be able to keep them for longer, this is done by "deep" cloning the data. This is already kinda automatic in my current approach since I use a Weak reference to the tracker.