Skip to content
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

Add per-scene UndoRedo #59564

Merged
merged 1 commit into from
Aug 22, 2022

Conversation

KoBeWi
Copy link
Member

@KoBeWi KoBeWi commented Mar 26, 2022

This is just a proof-of-concept, but looks promising so far:
(it's finished, just needs bugfixing)
godot windows tools 64_OKGXXfXsYF
Right now implemented only in scene tree dock and everything is completely broken xd It needs quite a bit work to finish this PR. But the idea is that there are minimal changes to usage of UndoRedo, everything is magically performed behind the scenes. Most of the changes right now come from the fact that EditorData returns a reference to UndoRedo, so it was accessed with . operator in some places and I had to change it.

How does this work:

  • editor has a single global UndoRedo instance that everything uses
  • the most common operations are creating actions and adding methods/properties
  • the idea is to replace this single UndoRedo object with another object with the same interface
  • almost every UndoRedo operation works on a specific object
  • based on that object, it's possible to infer a "context"
    • for nodes, it's the current scene. You can't operate on nodes from another scene
    • for internal resources, it's their owning scene's path
    • for external resources and everything else, there's a special "global" context

When you undo, the editor will again infer the current context (i.e. the current scene) and undo actions done on that scene. The global actions are mixed-in, I'm planning to add timestamps to actions, so that scenes and global operations can work together.

As I said, the PR is heavily in progress. The new system is hacked together with the old one. I added a method get_undo_redo2(), so that I can test parts of the code with the new system. I will remove the old UndoRedo once everything is finished.

Also, this does not change the old UndoRedo. It will still be used, but internally by the central EditorUndoRedoManager object. There is a chance that this all might fail at some point (i.e. there will be something that can't be easily replaced), but so far the work is smooth.

Closes godotengine/godot-proposals#1630
Fixes #45601

@ator-dev
Copy link
Contributor

ator-dev commented Mar 27, 2022

Good job, I definitely prefer your approach of using multiple UndoRedo objects; far more clean and object-oriented compared to multiple stacks in one UndoRedo. Would it be alright for me to integrate this into my system?

My PR #59544 uses my approach to multiple stacks in the background, but this can be replaced with your EditorUndoRedoManager approach while keeping the parts of my idea that work well (in my opinion).

@KoBeWi
Copy link
Member Author

KoBeWi commented Mar 27, 2022

Would it be alright for me to integrate this into my system?

Sure, whatever. I'm going to finish this anyway, so then we can just pick the implementation that works better.

EDIT:
Pushed more changes. CanvasItemEditor should now undo properly. I also changed animation editor, but trying to key a property crashes. I'll check that later.

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch from 6687771 to 1cc0494 Compare March 27, 2022 15:02
@ator-dev
Copy link
Contributor

One way in which our PRs differ is that you use a string (the file path?) to identify stacks, whereas I use the relevant ObjectID. Do you think the path string will work reliably enough for this usage? I'm just a bit concerned that it might stop working if something is saved to a different location, but maybe that makes it a different resource/scene anyway.

@KoBeWi
Copy link
Member Author

KoBeWi commented Mar 27, 2022

I might need to change that, right now it doesn't work properly with empty scenes. But using ObjectID would not really help either, I think.

@ator-dev
Copy link
Contributor

No, mine doesn't work for empty scenes either - it uses the ObjectID of the root node. My reason for preference is that an ObjectID is guaranteed to be unique, stay unique throughout the session, and not change for any given object (whereas I'm thinking that the string file path would change if a resource is saved somewhere else). There should also be a memory improvement - being an integer instead of a string - but of course this is negligible.

@ator-dev
Copy link
Contributor

Just something I'd like to bring up - there are some things that (so far as I can tell) cannot be made to work correctly without additional contributor effort. Both of our approaches detect context automatically where possible, but in cases such as renaming a node there is nothing passed to UndoRedo which can be sensibly used to detect the node being modified.

For my approach I'm thinking of having contributors add an extra line to pass in an object which can be used to detect context, such as the node being renamed, and you seem to use a similar approach with getting and passing in an UndoRedo object.

Are we happy with the effect of this on Godot development, and can it be mitigated? In most cases UndoRedo will work the same as before, but as soon as someone changes/adds an action so that context isn't easily detectable, it creates an opportunity for undo/redo to mysteriously fail in the future. This can't be tested for because undo/redo is all created on the fly.

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch from 1cc0494 to 90877d9 Compare March 27, 2022 22:16
@KoBeWi
Copy link
Member Author

KoBeWi commented Mar 27, 2022

EditorUndoRedoManager can take an optional context object in create_action() and if not provided it will guess the context from the first action. The worst thing that can happen is some scene-related action registered in global context. This will need some testing.

btw inspector and most editors are finished (still need fixing ofc).

@ator-dev
Copy link
Contributor

ator-dev commented Mar 27, 2022

EditorUndoRedoManager can take an optional context object in create_action() and if not provided it will guess the context from the first action

I see, that's what I'm doing as well, except that don't have a global context (at least in my current approach). I have a fail condition for if no context could be detected and none was passed in, which I think might help eliminate some confusion.

inspector and most editors are finished

Well done, sounds like it's going well (;
Would you mind trying mine out as well once you've got the time, in order to compare them? I was thinking we could take the best ideas from both PRs, since there are aspects of yours that I think work better than mine, and vice-versa. I'll also do some more extensive testing on yours; I'm a bit caught up in work right now, but I've got a holiday in a week so will have plenty of time then.

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch from 90877d9 to 1fcdd55 Compare March 28, 2022 01:05
@KoBeWi
Copy link
Member Author

KoBeWi commented Mar 28, 2022

Almost finished with moving stuff to the new system. Note that I'm just replacing UndoRedo * with Ref. Surprisingly it works in many cases, but lots of stuff is likely broken.

Would you mind trying mine out as well once you've got the time, in order to compare them?

I can check when I finish this.

I'll also do some more extensive testing on yours;

I'd wait until I mark the PR as ready for review.

EDIT:
Pushed more changes. Context is now based on "edited scene id" instead of String (bruh).
EditorData has internal EditedScene structure. Every opened scene tab has associated EditedScene and it (hopefully!) persists if the tabs are moved around, root is deleted or file is moved, so I added id to EditedScene and now context is determined by this value.

I also added Action struct to EditorUndoRedoManager. It's created for every action and holds action name, context id and timestamp. It's not stored nor used yet, but this will be needed later.

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch 5 times, most recently from f6186c2 to 4f22c81 Compare March 31, 2022 00:14
@KoBeWi
Copy link
Member Author

KoBeWi commented Mar 31, 2022

Ok timestamps and global/scene mixed contexts seem to work. So base functionality is roughly done and works as planned (although that's not surprise). Now onto fixing bugs and adding missing features... (like scene versioning)

@KoBeWi
Copy link
Member Author

KoBeWi commented Apr 3, 2022

Oof. I just pushed a commit that removes get_undo_redo2() and changes the base method to return Ref<EditorUndoRedoManager>. The amount of changes I had to do in different editors is insane. I wanted to avoid including EditorUndoRedoManager in the headers, so I changed to forward declarations everywhere (except editor inspector; no idea why it won't allow me). It would be much easier if the undoredo wasn't uselessly assigned to variables instead of just being used from the singleton :/

There was some code I couldn't port, so for now it's marked with /// TODO. The most difficult part probably starts now.

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch 2 times, most recently from 22da6fe to 5a1095e Compare April 9, 2022 19:38
@KoBeWi
Copy link
Member Author

KoBeWi commented Apr 9, 2022

I think I'm close to finishing this. After my latest changes it's possible to revert a scene without destroying whole undo history:
godot windows tools 64_7rik3usEcf

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch 4 times, most recently from 5044c09 to c663011 Compare August 10, 2022 15:43
@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch 2 times, most recently from c191665 to 741081c Compare August 20, 2022 14:27
<return type="int" />
<param index="0" name="object" type="Object" />
<description>
Returns the history id deduced from the given [param object]. It can be used with [method get_history_undo_redo].
Copy link
Member

Choose a reason for hiding this comment

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

That's a matter for a different PR, but I think we should harmonize our usage of "ID" and "id" in the classref.

$ rg -g'*.xml' " ID " | wc -l
122
$ rg -g'*.xml' " id " | wc -l
25

Copy link
Member

@akien-mga akien-mga left a comment

Choose a reason for hiding this comment

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

Left some non-blocking documentation comments, should be good to merge as is or once amended IMO.

@KoBeWi KoBeWi force-pushed the FINALLY,_ULTIMATE_UNDO_REDO branch from 741081c to ece3df3 Compare August 22, 2022 16:05
@akien-mga akien-mga merged commit b8a6431 into godotengine:master Aug 22, 2022
@akien-mga
Copy link
Member

Thanks!

@atirut-w
Copy link
Contributor

Is this coming to 3.x by any chance?

@Calinou
Copy link
Member

Calinou commented Aug 27, 2022

Is this coming to 3.x by any chance?

This relies on backwards-incompatible core refactoring, so I'm afraid not.

@KoBeWi
Copy link
Member Author

KoBeWi commented Aug 27, 2022

Compatibility breaking would only affect editor plugins. While EditorUndoRedoManager has almost the same interface as UndoRedo, it requires doing extra steps in some cases and obviously it would break if someone uses typing. But if such (rather minor IMO) breakage is acceptable, I guess it could be backported. But someone would have to do it and the PR is quite big, soo...

evanwalsh added a commit to evanwalsh/godot-ply that referenced this pull request Sep 18, 2022
jarneson added a commit to jarneson/godot-ply that referenced this pull request Oct 22, 2022
* Godot 4.0 beta 1 import file updates

* API rename: `update` is now `queue_redraw`

Per godotengine/godot#64377

* API rename: `x2y` functions are now `x_to_y`

Per godotengine/godot#64367

* Update types: `UndoRedo` is now an internal of `EditorUndoRedoManager`

Per godotengine/godot#59564

* beta 3

Co-authored-by: Jeff <jeffrey.arneson@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants