Mutable references to two different components in a system

#1

Hi everyone!

I’m working on a system that amongst others uses WriteStorage<MyComponent> in SystemData.
Therefore, all components of that type are being passed to the run-method as the parameter mut my_components.

Now I have some code that specifies two different entities that have a MyComponent attached to them:
let e1: Entity = ...
let e2: Entity = ...

With my_components.get_mut(e1) (or with e2) I can obtain a mutable reference to the associated component; for normal Rust-borrow-checker-reasons, the two mutable references cannot be sustained at once; that is, however, exactly what I need.

If faced with a standard Collection, there is split_at_mut that lets you deal with these kind of problems; that doesn’t work with the Storage-collections though.

How does one solve this problem? Transferring the Storage into, say, a Vec (which provides split_at_mut) seems a bit elaborate. Is there a simpler approach?

Thanks in advance,
Islion :slight_smile:

(Azriel Hoh) #2

Heya, there’s a number of solutions you could try, but not sure which is best – it depends on what you’re doing.

  • Read both values with storage.get(e1), storage.get(e2), calculate new values, then store them after.
  • There’s an unsafe fn unprotected_storage_mut(&mut self) -> &mut T::Storage, maybe it’s safe to mutate the data, just not insert/remove.
  • Try and extend Storage implementations to allow retrieving multiple &mut components given a set of entities.
2 Likes
#3

Thanks for your reply!

Since my original post, I have tried several approaches, here are my experiences with them:

I’m not sure whether I’m understanding you correctly, but those are my two interpretations of this approach:

  • anything involving cloning: that does work, and as I am not working in a performance-critical part of my program, this is actually okay. It doesn’t feel right though, as I don’t really want to juggle with new components.
  • saving the two references, transferring the storage into something with split_at_mut, then finding the saved references in there: as the components are referenced immutably, there may not be a mutable call to the storage, which kills this approach right away (I am no Rust-pro by far, maybe there’s a different way?).

That seems quite like the way to go; amethyst and specs don’t document this part of their API very well and I barely have a clue what to consider when working with the storage. But I surely will do some more digging.

Implementing a custom trait for Storage doesn’t give me access to its private fields, or am I wrong? Or do you mean, that I should edit Storage's actual source code right in place? Anyway, that would still leave me with my insufficient knowledge of Storage's internal workflows…

Lastly, I have tried simply removing the components from the storage, working with them, and at the end of run, I simply re-insert them. As with the clone-approach, this seems a bit overkill, but I think it’s more favourable because no large data copying is involved. I made a brief performance test:

  • get_mut twice, the mutating operation separated so there’s no borrow-conflict:
    hovered around 50μs
  • remove both components, then the one, correct mutating operation on both of them, then insert:
    fluctuated a lot, averaging around 70μs I’d say.

As I’d said, this is not very performance-critical code; but even if it was, the impact of remove/insert is barely noticeable in my context and I think, this will be the way to go for me. I wonder if this solution is safe concerning specs’ parallelism.

Anyway, thanks for your help!

(Azriel Hoh) #4

Hm, I was thinking something like this:

let (c1_new, c2_new) = {
    let c1 = components.get(first_entity).unwrap();
    let c2 = components.get(second_entity).unwrap();

    some_function(c1, c2) // this returns (Component, Component)
};

components.insert(first_entity, c1_new).unwrap();
components.insert(second_entity, c2_new).unwrap();

That way, the borrows that happen at the same time are immutable borrows, and they’re released before updating the component values.

I think it’s not too different from the clone approach in terms of performance, unless the new values are smaller than the component, and you can just patch the component like so:

let (c1_delta, c2_delta) = {
    let c1 = components.get(first_entity).unwrap();
    let c2 = components.get(second_entity).unwrap();

    some_function(c1, c2) // this returns something small, like (u32, u32)
};

components.get_mut(first_entity, c1_new).unwrap().x += c1_delta;
components.get_mut(second_entity, c1_new).unwrap().x += c2_delta;
1 Like
#5

Thanks for your response :slight_smile:

Your approach seems quite reasonable, both in performance and coding labour.
I could definitely implement this for my case;
the function I want to call (fn function(&mut self, other: &mut MyComponent); in MyComponent) presents a layer of abstraction I want to keep though. Inside my system, I don’t really want to get into the details of MyComponent, I just want to call that function.

I think I’ll stick with the remove-insert approach for the moment; if there’s a way to correctly obtain mutable references to two components, I would still like to know.