-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use transactional storage to enable mutation recovery. #19
Comments
I've been working on a crate that should help solve this issue as well as #17. It provides journaled collections, which behave in a similar manner to mutations in Plexus. For example: let mut hash = HashMap::new();
hash.insert("hello", "world".to_owned());
let mut hash = hash.journal(); // Begin transaction.
hash.insert("foo", "bar".to_owned());
for mut entry in hash.iter_mut() {
entry.to_mut().1 = "modified".to_owned();
}
let hash = hash.revert(); // End transaction. Adapters also provide a common iterator type per collection and optionally unify types. This allows for both journaled and unjournaled collections to implement traits. If used for storage in Plexus, then the mutation API can enable journaling and then commit or revert the log depending on whether or not a mesh mutation was successful. |
This would be a significantly different approach from the current mutation API. Like other issues related to storage, this would be greatly improved once GATs land in stable Rust. I will probably wait until that happens before exploring this any further. |
Another thought. This is only useful if:
I don't think that [1] should be allowed (keeping in mind that these mutations are topological). The real value of this model is if [2] ever happens. As the mutation API is implemented today, users would either have to clone graphs or accept total loss when a mutation fails. |
I'm closing this issue for now. It should be possible to create some kind of transactional storage and use it during mutations, but to accomplish this with the pub trait AsStorage<E>
where
E: Entity,
{
fn as_storage(&self) -> &dyn Storage<E>;
}
pub trait Storage<E>: Get<E> + ...
where
E: Entity,
{} Moreover, transactional storage could be expensive. Mutable I think the mutation API should remain internal and all public APIs should disallow any inconsistent topological mutations. Discarding the mesh is fine, as it should always lead to a panic, and such panics indicate internal bugs. It's also worth noting that geometric errors should not be allowed to occur within the mutation API, as they should be recoverable and not lead to panics or inconsistent topology. Unfortunately, some recent changes work against this goal by pushing geometric computation behind the mutation API. That will need to be fixed (cache types may need to perform geometric computation and store the results). |
I'm reopening this in the hopes it can be solved. I may be rehashing some thoughts that have already been stated, but here we go anyway. 😅
I've changed my mind about this. The mutations exposed by view types (for example, To accomplish this, commits must generally check consistency except for an internal API that does not (depending on configuration, such as Journaling slot maps presents problems. The ugly solution is to synthesize keys by examining the slot map's capacity and generating I'm hoping to play with this on a branch and update this issue accordingly. |
Unfortunately, it looks like this may be best solved with GATs (which also help avoid boxing iterators in the storage APIs). An implementation without GATs will require the |
The |
Here is an example of how gnarly key synthesis is. That code synthesizes slot map keys. Not only does this require This is gross, but I'll continue to implement this and see where it goes. |
I've been working on this for the past month or so and have ultimately come to the same conclusion: this probably shouldn't be done. Here's an important question: is a low level public mutation API actually useful? I don't think it is. A rough analogy would be a linked list data structure that exposes a transactional API that lets users manipulate nodes and their links and then perform a checked commit. 😕 I think the approach of exposing a higher level and infallible API is a good one. A transactional mutation API is still useful internally, even if it destroys a graph when it becomes inconsistent. If a failure occurs in a higher level API, then it's a bug and Plexus should panic by Importantly, exposing a "safe" mutation API with transactions and restorative failure modes involves compromises. The most notable compromises can be summarized as code complexity and performance. I'm very close to a complete transactional API at the cost of dynamic dispatch, rekeying, and significantly more complex storage and mutation APIs. There is also some abuse of the This work hasn't been a waste though. Many changes on the
And there are more. Removing restorative transactions allows for completely static dispatch, static iterators in traits (when enabling GATs), and much less complexity. Some features are useful but still incur a tradeoff. For example, dynamic dispatch to |
This change modifies the storage APIs in numerous ways to support journaling. `AsStorage` now refers to a type defined by the `Dispatch` trait, which can be an unsized trait object. This allows for dynamic dispatch, which in turn allows for storage types to be wrapped for journaling. Importantly, orphan views and mutable iteration are now more limited, exposing only user data (via the `Payload` trait) instead of an entire entity. Entity types like `Vertex` are no longer re-exported, and `get` and `get_mut` functions are used to get user data. See #19
This change introduces a `Mode` trait that determines the type of storage upon which `Mutation`s operate. This enables immediate and journaled mutations with different commit behaviors. See #19
The |
When the mutation API is used to modify a mesh, commit failures are catastrophic: the entire mesh is lost. Today, the results of these mutations are unwrapped, so not only is the mesh lost, but the thread panics.
The mutation API allows a replacement to be used when modifying through a mutable reference. To make these errors recoverable, a clone of the mesh could be used as a replacement, but this could be prohibitively expensive.
Instead, consider using transactional collections for storage. Such a collection would track modifications, present a view of those modifications, and allow them to either be committed or rolled back. If a topological error is detected by the mutation API, then storage can be rolled back and the original mesh can be recovered without too high a cost. It may be possible to refactor the transactional behavior of mutations and share it with storage.
The text was updated successfully, but these errors were encountered: