-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Unified identifer for entities & relations #9797
Unified identifer for entities & relations #9797
Conversation
I don't think
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Identifier {
hi: u32,
lo: u32,
} If we want them to be Also, the viability of using a bit pattern like this has already been proven by |
Having
|
None of these structs need
I'm specifically placing importance on keeping the impl Entity {
pub fn flags(&self) -> IdFlags
let id: Identifier = self.into();
id.flags()
}
} And checking specific flags shouldn't be part of either API. We should use the
Any other flag bits are outside the scope of creating a unified identifier for entities and relationships. Adding this |
I'm working on things already (thanks for the feedback!), but I've got some things to raise as I've worked on this so far:
Indeed, we can only go from
And thanks for that! Though I don't think I can leave Also, do we expose
As for |
Right, current state of the PR: Moved masking logic out of
|
Plenty of nits, but to be clear: I'm on board with this direction. This is an abstraction that we're going to want to reuse, and building out a tested, documented API for it now is a good way to split the work. |
|
||
assert_eq!( | ||
IdentifierMask::pack_kind_into_high(high, IdKind::Placeholder), | ||
0xC0FF_EEEE // Milk and no sugar, please. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may have had a bit too much fun coming up with test cases... 😄
Alright, this is looking good now :) Have I missed something, or should this come out of draft now? |
I just amended the changelog for the PR, but otherwise if everything more or less looks 'good' then I'll set the PR as ready for full review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good to me now, and I think that this is a step in the right direction. I'll defer to @maniwani about when we should merge this though.
In the meantime, can you run some benchmarks to make sure that this didn't regress performance? It should all just be transparently in-lined, but compilers are fickle.
I did a bunch of benchmarks, but my machine doesn't seem to be very... reliable at outputting numbers without huge variations between runs. My laptop is a Thinkpad A285, AMD Ryzen 2500U 4c/8t processor, 16GB of RAM, etc. In summary though, I see regressions on spawn commands between like 2-5% when my machine is not creating runs with like 50%-100% variations, and the spawn world ones are consistently like 20-30% regression when not having variations of like 100% every run. I basically redid as many of the benchmarks, closing as many apps until I could get something more consistent, but some of these still vary quite a bit per run. Here's what I've found:
EDIT: I redid some of the benchmarks to get values that are more consistent than the weird spikes of 100% that I keep getting from my laptop. |
I reconfigured my laptop a bit to be able to get more consistent result without the weird perf spikes, so I redid both the main/branch benches to see if I still got the same perf results as before. It seems things are not showing the same 20-30% perf regression any more:
Since my machine is not exactly super reliable, I ask someone else to perhaps benchmark this PR as well, just to confirm what I am seeing. |
Current main vs this branch:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the consistent ordering of index and generation that the constructor ensures but im a bit turned off by the loss of explicitly naming the fields. In practice I dont think this is an issue, as getting the order wrong will be very obviously broken most of the time. Performance seems unaffected (up to noise)
7704573
to
29ddce2
Compare
Going to see if we can get a fresh round of reviews to validate the merge with entity niching. |
To add further info for review: The generation increment now has to take into account masking in order to prevent overflowing into the flag bits. As such, there is additional overhead being introduced with increment to ensure this operation is safe and doesn't try to do any funny business should values greater than the ASM example link: https://rust.godbolt.org/z/4T9o4Gdc6 inc_generation_by:
and edi, 2147483647
and esi, 2147483647
lea ecx, [rsi + rdi]
mov eax, ecx
shr eax, 31
add eax, ecx
and eax, 2147483647
ret
inc_generation_by_one:
and edi, 2147483647
lea eax, [rdi + 1]
shr eax, 31
add eax, edi
inc eax
and eax, 2147483647
ret
old_inc_generation_by:
mov eax, edi
add eax, esi
adc eax, 0
ret Basically, at its core, the instruction count goes from 3 instructions to 7, but avoiding any branches in the process. For increments of one, this becomes 6 instructions. In general though, perf seems unaffected much by this, since generation incrementing does not appear to be on a hot path. I tested with the Other benches show that the hashing and spawning remains more or less the same, with no noticeable regressions and everything within noise thresholds: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Co-authored-by: vero <email@atlasdostal.com>
Objective
The purpose of this PR is to begin putting together a unified identifier structure that can be used by entities and later components (as entities) as well as relationship pairs for relations, to enable all of these to be able to use the same storages. For the moment, to keep things small and focused, only
Entity
is being changed to make use of the newIdentifier
type, keepingEntity
's API and serialization/deserialization the same. Further changes are for follow-up PRs.Solution
Identifier
is a wrapper aroundu64
split into twou32
segments with the idea of being generalised to not impose restrictions on variants. That is forEntity
to do. Instead, it is a general API for taking bits to then merge and map into au64
integer. It exposes low/high methods to return the two value portions asu32
integers, with then the MSB masked for usage as a type flag, enabling entity kind discrimination and future activation/deactivation semantics.The layout in this PR for
Identifier
is described as below, going from MSB -> LSB.The high component in this implementation has only 31 bits, but that still leaves 2^31 or 2,147,483,648 values that can be stored still, more than enough for any generation/relation kinds/etc usage. The low part is a full 32-bit index. The flags allow for 1 bit to be used for entity/pair discrimination, as these have different usages for the low/high portions of the
Identifier
. More bits can be reserved for more variants or activation/deactivation purposes, but this currently has no use in bevy.More bits could be reserved for future features at the cost of bits for the high component, so how much to reserve is up for discussion. Also, naming of the struct and methods are also subject to further bikeshedding and feedback.
Also, because IDs can have different variants, I wonder if
Entity::from_bits
needs to return aResult
instead of potentially panicking on receiving an invalid ID.PR is provided as an early WIP to obtain feedback and notes on whether this approach is viable.
Changelog
Added
New
Identifier
struct for unifying IDs.Changed
Entity
changed to use newIdentifier
/IdentifierMask
as the underlying ID logic.