Command design pattern - modify position

I'm implementing an editor in bevy, and want to use the command design pattern so it's easy to implement undo in the future. In my editor, points can be created, modified or deleted. I understand how create and delete would work, but how would I implement modify, when a point could for example be dragged to a new position across multiple frames? I don't want to push thousands of modify actions to the command stack, so how do you suggest I implement this?

You might consider defining what is one "committed" action by the user. Add and delete are immediately committed, but move might create a "tentative" move entry. As the user continues to move, just update/replace that tentative entry. Then the user can release the mouse (or otherwise signal commit) to commit that entry. The next move would then start another tentative move.

How you accomplish this in your current setup in Rust, is another question. Does your current setup allow adding the concept of a "tentative" entry and a way of "updating" it?

1 Like

I haven't implemented any of the command design pattern yet, currently the positions etc of points are modified in different places across the application and it's all starting to get a bit disorganised. I was thinking I could do it with a trait (pseudocode):

trait CommandItem {
    fn execute(&mut self, &mut World);
    fn unexecute(&mut self, &mut World);
}

I could store different actions e.g.

struct CreatePoint {
    position: Vec3,
    entity: Option<Entity>,
   // ...
}
impl CommandItem for CreatePoint {
    fn execute(&mut self, &mut World) {
        let id = world.spawn(Transform {translation: self.position, ..default()});
        self.entity = Some(id);
    }
    fn unexecute(&mut self, &mut World) {
        let Some(id) = self.entity else {return};
        world.entity(id).despawn();
    }
}

Then I could store this as a stack which could be accessed by a system which takes in events sent out elsewhere

#[derive(Event)]
struct CommandEvent(Box<dyn CommandItem>);

fn command_executor(
    world: &mut World, 
    command_stack: Local<Stack<Box<dyn CommandItem>>>, 
    command_events: EventReader<CommandEvent>,
) {
    for ev in command_events.read() {
        // execute the command
    }
}

How do you think the idea of 'tentative' entries could fit into this?
I'm using egui for the UI layer of my app, and currently just modify the transforms directly by passing mutable references to transforms into egui (and accessing them through queries in my bevy systems). I'm also using egui-gizmo and I don't know how I would make this all fit in with that.
Not sure if this is even the best approach to implementing the command desing pattern in Rust, just one that I thought of, so let me know what you think!

This playground illustrates the rough idea.
Your event needs a flag (here I chose an enum, could be a struct like before, with a Boolean flag or similar) and then only add to the stack if the flag says it's a commit.
If the event is not a commit, then remember it to undo it when the next event occurs. With this setup, you would need a final "commit" event to trigger when the user stops dragging. Otherwise, dragging would never become permanent.

1 Like

Thank you very much for taking the time to write this up and help me, I really do appreciate it.

Since the command_executor function would run once per frame, taking in any events emmitted by the UI layer which runs before it in the frame (so realistically there'll only be one event in command_events), I'm assuming I'd need to make the tentative variable a local in the function so that its state is persistant across multiple frames?

Also, I'm still not fully understanding how I would implement this for something like a gizmo. My gizmo takes in a transform, and outputs another transform that's been modified by the user in some way. I imagine a command for that would look something like this:

struct ModifyTransform {
    before: Transform,
    after: Transform,
    entity: Entity
}
impl CommandItem for ModifyTransform {
    fn execute(&mut self, &mut World) {
        // set the entity's transform to 'after'
    }
    fn unexecute(&mut self, &mut World) {
        // set the entity's transform to 'before'
    }
}

I'd save the transform before the gizmo, and send out an event containing the 'before' and the 'after' transforms if the gizmo has been interacted with.
However, surely if I undo this Modify action once committed, it would take the transform back to what it was last frame, not before the drag action started?
Is there a better way of implementing the Modify action which I'm not understanding (which doesn't involve putting the burden of storing the transform before the drag action on the UI layer)?

I also have the problem of Entity references stored in Command structs becoming stale, because undoing and redoing a Create action would mean the Entity's ID would be different, causing issues if Entity references are stored in Command actions further up the undo stack.

Thanks again for all your help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.