Closed Imberflur closed 11 months ago
Exciting news! I finally got a chance to update veloren to use this and profile it and I don't see any particular regressions. Might try to post some tracy pictures later.
I think this should be ready to merge soon.
Here are some profiling results in veloren, "this trace" is before changes here and "external trace" is after. I focused on two systems. "character_behavior" which has a join over a lot of component types and "phys" which has multiple joins over fewer component types of which some joins are parallel ones. It seems like there are no regressions and potentially a slight improvement (the profiling conditions have room to be more strictly controlled so I would not trust that this improvement is as significant as it appears here).
@xMAC94x thanks for taking a look :heart:
I will wait till this weekend in case anyone else is interested in reviewing. If anyone interested needs more time just let me know.
I just wanted to say: thanks for pushing this all the way to completion :) I would have reviewed, but I've been busy with other things recently. Nice work!
Fixes https://github.com/amethyst/specs/issues/647
See API changes section for description.
Checklist
Todo
LendJoin usesSo I wanted to removenougat
since I started working on it before GATs stabilized. I kept it because I thought it might be useful to add more iterator combinators (some combinators are currently impossible to implement with actual GATs). However, I think it would probably be prudent to remove this and use regular GATs for simplicity.nougat
but it turns out that I can't implementfor_each
without it (without either running into requiringSelf: 'static
due tofor<'_>
hrtb or not being able to have lifetimes in the GAT (LendJoin::Type<'next>
) that aren't'next
and aren't'static
.specs
usingecs_bench_suite
, there is maybe a 3% increase inadd_remove
, there was a significant increase (10+%) in deletion times due toshred::MetaTable
changes, so I introduce anightly
feature that enables a more efficient implementation usingptr_metadata
,fetch
heavy code shows improvements due to the use ofatomic_refcell
inshred
instead of the custom atomic cell implementation)hibitset
andshred
to crates.io and use those instead of git dependencies.API changes
This is a breaking change.
This PR fixes several soundness issues that I encountered when working on #737. These include:
Join
implementation forspecs::storage::Entries
allows creating aliasing&mut
references to the underlying storage (i.e. when not using theJoinIter
as a lending/streaming iterator).Join
implementation and API ofspecs::storage::RestrictedStorage
similarly can create mutable aliasing references to storages as well as to a specific component.Join
implementation ofStorage
allowedDerefFlaggedStorage
to create aliasing&mut
references to the internal events channel.JoinIter::get
allows creating aliasing mutable refs, see #647.&mut Join::Value
references.Many (but not all) of these issues stem from artificially lengthening the lifetime of the
&mut Join::Value
withinJoin::get
implementations. We now avoid this and leverage alternative mechanisms including interior mutability and lending iteration.In more detail, we:
LendJoin
trait or lending (aka streaming) iteration of joined values.Entries
now only implementLendJoin
(i.e. remove unsoundJoin
implementations).LendJoin
for everything that implementsJoin
(manually, not a blanket impl since there are difference in the implementation).JoinIter::get
toJoinLendIter::get
.RepeatableLendGet
trait (whichJoinLendIter::get
requires). This is to facilitate destructive implementations ofLendJoin
where getting the same index more than once would be unsound (i.e. literally just the owned implementation ofLendJoin
forChangeSet
which removes values as it iterates).ParJoin
trait to not rely onJoin
implementation so we have additional flexibility to use different implementations and keep&mut Join::Value
inJoin::get
.Join
anunsafe
trait. The newLendJoin
trait is alsounsafe
to implement.Storage
no longer implements mutableJoin
s (the non-lending variant) for allUnprotectedStorage
s. Instead this implementation requires that the storage implementsSharedGetMutStorage
. The trait which provides ashared_get_mut(&self, id: Index)
method. Notice this takes a shared reference. Storages that can soundly implement this wrap their components inUnsafeCell
to allow constructing a mutable reference from a shared reference.#![deny(unsafe_op_in_unsafe_fn)]
to make it easier to identify and document unsafe operations.To try to catch any remaining UB, I ran the available tests under Miri . This also identified some issues in dependencies for which I have submitted PRs to fix:
(We need to publish new versions of these to crates.io)
This PR adds Miri to the CI.
Additionally,
specs
exposed anightly
cargo feature that enabled additional APIs using GATs. Since GATs are now stabilized, I bumped the MSRV to be able to eliminate this feature and remove a bunch ofcfg
complexity.Also I introduced
AccessMut
trait which is similar toDerefMut
except it requires explicit use. The associated typeUnprotectedStorage::AccessMut<'_>
now requiresAccessMut
instead ofDerefMut
. This is to faciliate my work in https://github.com/amethyst/specs/pull/737 where I am exploring a flagged storage type that makes generating modification events more explicit. A blanket implementation ofAccessMut
for anything implementingDerefMut
is included.