Skip to content

Rollback marker#129

Open
johanhelsing wants to merge 2 commits intogschup:mainfrom
johanhelsing:rollback-marker
Open

Rollback marker#129
johanhelsing wants to merge 2 commits intogschup:mainfrom
johanhelsing:rollback-marker

Conversation

@johanhelsing
Copy link
Collaborator

@johanhelsing johanhelsing commented Feb 22, 2026

This effectively renames the old Rollback to RollbackId, and creates a
new unit struct Rollback

So the new flow is:

  1. Insert Rollback on an entity in any way you want (required
    components, scenes, spawning.
  2. The OnAdd hook handles creating a RollbackId(Entity) which is
    what we use internally for ordering entities.

This had the added overhead of having two components per rollback
entity. So a redundant entity list basically. But it allows much better
ergonomics, and scene authoring workflows, scripting interations++.

Before:

#[derive(Component)
struct Player;

// Have to remember to explicitly add rollback anywhere Player is spawned
commands.spawn((Player, Sprite {}).add_rollback();

Now:

#[derive(Component)
#[require(Rollback)
struct Player;

// Rollback added automatically :)
commands.spawn((Player, Sprite {});

Or simply:

app.register_required_components::<RigidBody, Rollback>();

or

app.require_rollback::<RigidBody>(); 

Then all movable physics objects will be rolled back. Don't have to remember to add rollback anywhere.

If you prefer to be explicit about it, you could still do:

commands.spawn((Player, Rollback, Sprite {}));

@johanhelsing johanhelsing requested a review from gschup February 22, 2026 22:39
@johanhelsing
Copy link
Collaborator Author

This is untested in anything but the tests in this repo, but I'd like some feedback. I think it improves ergonomics significantly, and in my case the performance tradeoff and slightly uglier internals is definitely worth it.

@johanhelsing
Copy link
Collaborator Author

@bushrat011899 since you wrote the original code, in case you are still interested in this :)

This effectively renames the old Rollback to RollbackId, and creates a
new unit struct `Rollback`

So the new flow is:

1. Insert `Rollback` on an entity in any way you want (required
   components, scenes, spawning.
2. The `OnAdd` hook handles creating a `RollbackId(Entity)` which is
   what we use internally for ordering entities.

This had the added overhead of having two components per rollback
entity. So a redundant entity list basically. But it allows much better
ergonomics, and scene authoring workflows, scripting interations++.

Before:

```rust
#[derive(Component)
struct Player;

// Have to remember to explicitly add rollback anywhere Player is
// spawned
commands.spawn((Player, Sprite {}).add_rollback();
```

Now:

```rust
#[derive(Component)
#[require(Rollback)
struct Player;

// Rollback added automatically :)
commands.spawn((Player, Sprite {});
```

Or simply:

```rust
app.register_required_components::<RigidBody, Rollback>();
```

Then all movable physics objects will be rolled back. Don't have to
remember to add rollback anywhere.
Sugar for:

```rust
app.register_required_components::<Component, Rollback>();
```
@johanhelsing
Copy link
Collaborator Author

Would also be possible to remove the Rollback component and have the following API:

app.rollback_entities_with::<Player>()
   .rollback_component_with_copy::<Player>()
   .rollback_component_with_copy::<Transform>()

It would register an OnAdd<Player> hook that does the same thing. If an entity has multiple rollback marker components, they would redundantly try to add rollback for the entity, wasteful, but not sure if that's really a problem though.

@johanhelsing
Copy link
Collaborator Author

johanhelsing commented Feb 23, 2026

... or we could potentially support the following API:

#[derive(Default, Reflect, Component, Clone, Copy, Deref, DerefMut, Rollback)]
#[rollback(copy, marker)]
#[reflect(Rollback)]
pub struct Velocity(pub Vec3);

#[derive(Resource, Default, Reflect, Hash, Clone, Copy, Rollback)]
#[rollback(resource, copy, checksum)]
#[reflect(Hash, Rollback)]
pub struct FrameCount {
    pub frame: u32,
}

This would auto-detect through reflect, and require no manual registration through app command extensions.

What I like about this is that everything about a type is in one place. It's harder to forget rollback registration when creating a new component alongside other components that already have it, and as a crate author, i can easily provide a default rollback and snapshotting strategy.

If reflect auto-reflect registration is disabled, the following would still work:

app.rollback::<Velocity>();
app.rollback::<FrameCount>();
app.rollback_component_with_copy::<Transform>();

Since we control the underlying trait, we probably implement RollbackRegistration for common built-in bevy types types, and get:

app.rollback::<Transform>(); // registers both hash and copy strategies without the user having to think about it.

Maybe it's too much magic? What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant