Skip to content

Conversation

Shatur
Copy link
Contributor

@Shatur Shatur commented Sep 27, 2025

Objective

Entity serialization is necessary for networking. Entities can exist inside components and events. After deserialization, we simply map remote entities to local entities.

To serialize entities efficiently, we split them into index and generation, which benefits from varint serialization. #19121 and #18704 changed the entity layout, and I like the new layout a lot. We can now use the extra bit from the index to store whether the generation is zero or not, avoiding the need to serialize the generation entirely.

However, constructing new entities requires relying on the internal layout, which is not very ergonomic. For example, here is how an entity with index = 1 and generation = 1 can be created:

let expected_entity = Entity::from_bits((1 ^ u32::MAX) as u64 | (1 << 32));

Solution

  • Make Entity::from_raw_and_generation public. While at it, I also removed outdated comment.
  • Add EntityRow::from_raw_u32 to make the initialization nicer.

Testing

  • It's a trivial change, but I re-used EntityRow::from_raw_u32 in unit tests to simplify them.

Notes

I'd probably rename Entity::from_raw_and_generation into Entity::from_row_and_generation or Entity::from_index_and_generation.

Otherwise creation with index = 1 and generation = 1 looks like this:
```rust
let expected_entity = Entity::from_bits((1 ^ u32::MAX) as u64 | (1 << 32));
```

I also removed outdated comment which left untouched after the
generation rework (bevyengine#19121)
Make construction more convenient and doesn't require to pull `nonmax`.
@Shatur Shatur added this to the 0.17 milestone Sep 27, 2025
@Shatur Shatur changed the title More entity pub Make Entity construction more ergonomic Sep 27, 2025
@TrialDragon TrialDragon added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 28, 2025
Shatur added a commit to simgine/bevy_replicon that referenced this pull request Sep 28, 2025
For details see bevyengine/bevy#19121
bevyengine/bevy#18704

The niche is now in the index, which makes the compression logic even
simpler.

The index now represented by NonMaxU32, which internally represented as
NonZeroU32 with all bits reversed, so we have to xor the bits.

I opened a PR to make it more ergonomic and avoid us relying on the
internal layout: bevyengine/bevy#21246
@janhohenheim
Copy link
Member

I don't see how from_raw_u32 simplifies the API, but maybe I'm missing something? Making from_raw_and_generation pub sounds like a good idea though :)

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm comfortable making this pub, and I really appreciate how much the new helper function improves things. The value for tests is evident!

@alice-i-cecile
Copy link
Member

I don't see how from_raw_u32 simplifies the API, but maybe I'm missing something?

Take a look at the diff: it makes writing tests a bit more ergonomic, since you have to unwrap the NonMaxU32 you create anyways.

@janhohenheim
Copy link
Member

janhohenheim commented Sep 28, 2025

Yeah that's what I mean. You unwrap either way and type nearly the same amount of characters. You just shift the error from the type system guarantees to the function.

Anyhoot, not gonna block on that if others like it :)

@janhohenheim janhohenheim added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 28, 2025
@alice-i-cecile
Copy link
Member

I really like having one less set of parantheses :) Long method names are much easier to autocomplete!

@janhohenheim
Copy link
Member

Fair point!

@Shatur
Copy link
Contributor Author

Shatur commented Sep 28, 2025

Also new requires adding nomax crate in order to use it :)
That's the main reason I added from_raw_u32.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 28, 2025
Merged via the queue into bevyengine:main with commit e33ec1c Sep 28, 2025
44 checks passed
@janhohenheim
Copy link
Member

janhohenheim commented Sep 28, 2025

Also new requires adding nomax crate

okay that convinced me instantly that this is the right way to go haha
no need for users to directly depend on that.

mockersf pushed a commit that referenced this pull request Sep 28, 2025
# Objective

Entity serialization is necessary for networking. Entities can exist
inside components and events. After deserialization, we simply map
remote entities to local entities.

To serialize entities efficiently, we split them into index and
generation, which benefits from varint serialization. #19121 and #18704
changed the entity layout, and I like the new layout a lot. We can now
use the extra bit from the index to store whether the generation is zero
or not, avoiding the need to serialize the generation entirely.

However, constructing new entities requires relying on the internal
layout, which is not very ergonomic. For example, here is how an entity
with index = 1 and generation = 1 can be created:

```rust
let expected_entity = Entity::from_bits((1 ^ u32::MAX) as u64 | (1 << 32));
```

## Solution

- Make `Entity::from_raw_and_generation` public. While at it, I also
removed outdated comment.
- Add `EntityRow::from_raw_u32` to make the initialization nicer.

## Testing

- It's a trivial change, but I re-used `EntityRow::from_raw_u32` in unit
tests to simplify them.

## Notes

I'd probably rename `Entity::from_raw_and_generation` into
`Entity::from_row_and_generation` or
`Entity::from_index_and_generation`.
mockersf pushed a commit that referenced this pull request Sep 28, 2025
# Objective

Entity serialization is necessary for networking. Entities can exist
inside components and events. After deserialization, we simply map
remote entities to local entities.

To serialize entities efficiently, we split them into index and
generation, which benefits from varint serialization. #19121 and #18704
changed the entity layout, and I like the new layout a lot. We can now
use the extra bit from the index to store whether the generation is zero
or not, avoiding the need to serialize the generation entirely.

However, constructing new entities requires relying on the internal
layout, which is not very ergonomic. For example, here is how an entity
with index = 1 and generation = 1 can be created:

```rust
let expected_entity = Entity::from_bits((1 ^ u32::MAX) as u64 | (1 << 32));
```

## Solution

- Make `Entity::from_raw_and_generation` public. While at it, I also
removed outdated comment.
- Add `EntityRow::from_raw_u32` to make the initialization nicer.

## Testing

- It's a trivial change, but I re-used `EntityRow::from_raw_u32` in unit
tests to simplify them.

## Notes

I'd probably rename `Entity::from_raw_and_generation` into
`Entity::from_row_and_generation` or
`Entity::from_index_and_generation`.
Shatur added a commit to simgine/bevy_replicon that referenced this pull request Sep 30, 2025
For details see bevyengine/bevy#19121
bevyengine/bevy#18704

The niche is now in the index, which makes the compression logic even
simpler.

The index now represented by NonMaxU32, which internally represented as
NonZeroU32 with all bits reversed, so we have to xor the bits.

I opened a PR to make it more ergonomic and avoid us relying on the
internal layout: bevyengine/bevy#21246
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants