-
-
Notifications
You must be signed in to change notification settings - Fork 22.7k
Reduce TileMapLayerEditor's undo/redo memory usage #107969
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
base: master
Are you sure you want to change the base?
Reduce TileMapLayerEditor's undo/redo memory usage #107969
Conversation
Hey, thanks for the contribution. While I think the added check is welcome, I think this PR is too much code for what it is trying to solve. I prefer we waste more memory than needed than adding almost 200 lines of code to the editor. That being said, I think we can at least keep the change about preventing a cell to be added if it does not cause any change. I'd suggest we keep the approach where you wrap the check in a macro (could be a function too), to conditionally add things to undo_redo. Also, note that you should avoid allocating new arrays like you did, this can harm performances. That being said, if you absolutely want this optimization to be done. I think a better approach would be to see how TileMapLayer data is serialized into an array to be stored as a property via |
btw this reminds me of this comment: #60836 (comment) |
While I agree on principle, it's still a lot of memory. And I believe the implementation isn't some unsalvageable spaghetti code and we can make it maintainable enough for the editor team to be comfortable with it.
I think the amount of memory saved here far outweighs the performance loss of allocating like, 4 additional temporary Vectors. Do keep in mind that adding methods to EditorUndoRedoManager is in itself an allocation and there is an allocation for every And if the temporary Vectors are really that much of a concern, we could make them member variables of TileMapLayerEditor and be even more optimized with our memory usage.
I think these are different use cases. TileMapLayer's serialized format is just a tightly-packed but uncompressed bitstream, and it's fine because it's used in runtime, load time is a priority, and the compression rate for an actual compression algorithm would be pretty shaky anyway. TileMapLayerEditor's operations however have properties that make them have better compression rates even with just run-length encoding. As mentioned, they usually result in edits that cover contiguous regions of same tiles. |
I mean, sorry, but the TileMap editor is already full of these kind of special optimizations here and there, for cases that do matter a lot more as they make the editor unusable otherwise. While I agree it can be a lot of memory, the added complexity here is not an acceptable tradeoff to me, considering how complex the editor already is. It would be fine if the TileMap editor wasn't that complex though, I agree the code is likely fine enough in itself. Just not in this context.
4 temporary vectors that could could contains thousands of tiles. If it can be avoided, it's better. But yeah, I do agree it's already stored as a vector under the hood anyway, so it could be fine.
I mean, I am not sure I understand here. Your implementation does not really add compression either. And both have the objective to store things more or less as compact as possible. So I think to avoid impacting the codebase too much, this is an acceptable tradeoff. |
Yes it does. It merges single-tile edits into rectangular edits. That's compression. It's a form of run-length encoding in 2D. Which is to say, an edit that turns this:
Into this:
Is compressed into two commands (pseudocode for brevity) and submitted to EditorUndoRedoManager:
And
|
Ok I see. I still don't think it's worth the complexity, but I do agree in the rect and the bucket-contiguous operations it would help reducing the memory usage. |
32a2a52
to
09a2dec
Compare
I've removed everything else aside from the redundant edit elimination, and I've folded the macro into a function and made Hopefully this is simple enough to be acceptable. |
09a2dec
to
7143bfc
Compare
@@ -52,6 +52,12 @@ TileMapLayer *TileMapLayerSubEditorPlugin::_get_edited_layer() const { | |||
return ObjectDB::get_instance<TileMapLayer>(edited_tile_map_layer_id); | |||
} | |||
|
|||
void TileMapLayerSubEditorPlugin::_add_to_output_if_tile_changed(HashMap<Vector2i, TileMapCell> &p_output, const TileMapLayer *p_layer, Vector2i p_coords, TileMapCell p_cell) { |
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.
To avoid unneeded copy:
void TileMapLayerSubEditorPlugin::_add_to_output_if_tile_changed(HashMap<Vector2i, TileMapCell> &p_output, const TileMapLayer *p_layer, Vector2i p_coords, TileMapCell p_cell) { | |
void TileMapLayerSubEditorPlugin::_add_to_output_if_tile_changed(HashMap<Vector2i, TileMapCell> &p_output, const TileMapLayer *p_layer, const Vector2i &p_coords, const TileMapCell &p_cell) { |
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.
Vector2i and TileMapCell are both merely 8 bytes. Passing by copy is likely going to be cheaper than passing by reference.
(It's like, you wouldn't normally use const int&
, or const uint64_t&
right?)
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 guess it's more for consistency TBH (and I guess future-proofing in case we increase those). But anyway, up to you, both work I guess.
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.
We don't do it for simple types (int
, bool
, etc.) but we generally do for complex types because they might have constructors etc., but the result depends on how the original variable is stored, if it's a temporary it's probably cheaper to pass by copy, but if it's a local value there's an additional dereference and copy when passing by value rather than by reference
Aside from the small nitpick above and CI not passing (looks unrelated to your change though, so you might just have to rebase on latest master), it looks good to me! |
7143bfc
to
ffa3b0a
Compare
Fixes #107853
This PR heavily reduces the amount of undo/redo memory used byTileMapLayerEditor
with two optimizations.Edits that have no effect (cell is same before and after) are not recorded inEditorUndoRedoManager
.Mostly helps with redundant operations like clicking the bucket tool on the same tile multiple times.Edits are compressed into rectangular regions (TileMapLayerEditor::Rect
) before being recorded inEditorUndoRedoManager
.Helps with bucket and rect tools, whose edits tend to cover large, contiguous regions.Improvement is immediately obvious with the bucket tool, but other tools can also benefit from this optimization to different degrees.This PR now only eliminates redundant edits.