GPv3: Reimplement how customdata is updated when layers are re-ordered #113962

Merged
Falk David merged 7 commits from Douglas-Paul/blender:gpv3-customdata-reordering-alternative-approach into main 2023-10-21 16:47:25 +02:00
Contributor

Instead of calculating the expected insertion index for a customdata entry being moved, this implementation just records the initial layer indices so that it can compare them with the final indices to determine how the customdata entries need to be re-arranged.


This PR supersedes #113874. If this one is merged, that one should be closed.

The first commit fixes a bug where grow_or_init_customdata() was calculating on an insertion index when it should not have been. Since GreasePencil::add_layer() always adds the new layer at the top, grow_or_init_custom_data() should always add the new customdata entry at the end.

The second commit is the focus of this PR. With the above bug fixed, the only thing that the insertion index was used for was to prepare a mapping from the old layer indices to the new ones (to be passed to reorder_customdata()). This implementation instead builds that map based on a "snapshot" of the layer indices taken before the layers are re-ordered. That eliminates the need to calculate an insertion index at all, which allows a lot of logic to be deleted.

Another benefit of this approach is that it works just as well if multiple layers are moved at the same time—it should be able to support the movement of entire layer groups.

Instead of calculating the expected insertion index for a customdata entry being moved, this implementation just records the initial layer indices so that it can compare them with the final indices to determine how the customdata entries need to be re-arranged. --- This PR supersedes #113874. If this one is merged, that one should be closed. The first commit fixes a bug where `grow_or_init_customdata()` was calculating on an insertion index when it should not have been. Since `GreasePencil::add_layer()` always adds the new layer at the top, `grow_or_init_custom_data()` should always add the new customdata entry at the end. The second commit is the focus of this PR. With the above bug fixed, the only thing that the insertion index was used for was to prepare a mapping from the old layer indices to the new ones (to be passed to `reorder_customdata()`). This implementation instead builds that map based on a "snapshot" of the layer indices taken before the layers are re-ordered. That eliminates the need to calculate an insertion index at all, which allows a lot of logic to be deleted. Another benefit of this approach is that it works just as well if multiple layers are moved at the same time—it should be able to support the movement of entire layer groups.
Douglas Paul added 2 commits 2023-10-20 08:45:54 +02:00
20292b016d Fix grow_customdata() to always insert at the end
Previously, grow_customdata() accepted a parameter for the location
at which the new entry should be inserted, but that was not
appropriate because GreasePencil::add_layer() always adds the new
layer at the top. (The layer is separately moved to the correct
position in the tree via GreasePencil::move_node_after() when
appropriate.)
1c8d11283a Reimplement how customdata is updated when layers are re-ordered
Instead of calculating the expected insertion index for a
customdata entry being moved, this implementation just records the
initial layer indices so that it can compare them with the final
indices to determine how the customdata entries need to be re-
arranged.
Douglas Paul reviewed 2023-10-20 08:50:58 +02:00
Douglas Paul reviewed 2023-10-20 09:04:47 +02:00
Douglas Paul requested review from Falk David 2023-10-20 09:15:53 +02:00
Author
Contributor

Bah, I can't figure out how to do comments on multiple lines of code in Gitea, so I'll just ask my questions here:

  1. Could the implementation of grow_customdata() be simplified even further now that it only ever inserts at the end?
  2. In my new implementation of reorder_layer_data(), is it too risky to use pointers as map keys? I don't know how likely or unlikely it would be for an an operation that changes the order of the layers to move them around in memory. If that's a concern, all that's really needed is a unique identifier each layer before & after the layer order is changed.
Bah, I can't figure out how to do comments on multiple lines of code in Gitea, so I'll just ask my questions here: 1. Could the implementation of `grow_customdata()` be simplified even further now that it only ever inserts at the end? 2. In my new implementation of `reorder_layer_data()`, is it too risky to use pointers as map keys? I don't know how likely or unlikely it would be for an an operation that changes the order of the layers to move them around in memory. If that's a concern, all that's really needed is a unique identifier each layer before & after the layer order is changed.
Falk David requested changes 2023-10-20 10:54:00 +02:00
Falk David left a comment
Member

Thanks for working on this, makes the code so much cleaner :)

I think there is one bug in your code right now that needs fixing. And I left some smaller cleanup comments.

Thanks for working on this, makes the code so much cleaner :) I think there is one bug in your code right now that needs fixing. And I left some smaller cleanup comments.
@ -2113,3 +2067,4 @@
static void grow_or_init_customdata(GreasePencil *grease_pencil)
{
using namespace blender;
const Span<const bke::greasepencil::Layer *> layers = grease_pencil->layers();
Member

I think with what you're going for, this if/else can just be replaced by CustomData_realloc(&grease_pencil->layers_data, layers.size(), layers.size() + 1); and the static function grow_customdata is no longer needed.

I think with what you're going for, this `if`/`else` can just be replaced by `CustomData_realloc(&grease_pencil->layers_data, layers.size(), layers.size() + 1);` and the static function `grow_customdata` is no longer needed.
Author
Contributor

Great, thanks. With this change grow_or_init_customdata() is now just 2 simple lines, so maybe not worth its weight as a function? Not sure where the Blender team lands on the tradeoffs on that sort of thing. I've left it for now.

Great, thanks. With this change `grow_or_init_customdata()` is now just 2 simple lines, so maybe not worth its weight as a function? Not sure where the Blender team lands on the tradeoffs on that sort of thing. I've left it for now.
Member

Yea and grow_or_init_customdata isn't used in many places either, so we can remove that one now too :)
I'm happy to see that we can remove a lot of code complexity here 👍

Yea and `grow_or_init_customdata` isn't used in many places either, so we can remove that one now too :) I'm happy to see that we can remove a lot of code complexity here 👍
@ -2128,3 +2082,3 @@
using namespace blender;
std::string unique_name = unique_layer_name(*this, name);
grow_or_init_customdata(this, parent_group);
grow_or_init_customdata(this);
Member

This now grows the custom data by one, but the layer might not be at the end. It's added to the end of the parent_group which doesn't have to be the root. So I assume that there needs to be some reorder_custom_data call here as well that shifts things in the right place.

This now grows the custom data by one, but the layer might not be at the end. It's added to the end of the `parent_group` which doesn't have to be the root. So I assume that there needs to be some `reorder_custom_data` call here as well that shifts things in the right place.
Author
Contributor

Ack, yes, very glad you caught this because I had completely forgotten to deal with it.

Most places that add a layer were adding the layer to the root group anyway, so I added some signatures of add_layer() that implicitly add the layer to the root group (and append the custom data at the end). And I made these existing signatures of add_layer() (that accept a group) into basically convenience methods: They just add the layer to the root group and then move it into the requested group via move_node_into().

Ack, yes, very glad you caught this because I had completely forgotten to deal with it. Most places that add a layer were adding the layer to the root group anyway, so I added some signatures of `add_layer()` that implicitly add the layer to the root group (and append the custom data at the end). And I made these existing signatures of `add_layer()` (that accept a group) into basically convenience methods: They just add the layer to the root group and then move it into the requested group via `move_node_into()`.
Member

Right, I just read through the changes and I like the way you resolved this. Just calling move_node_into for the convinience function makes a lot of sense.

Right, I just read through the changes and I like the way you resolved this. Just calling `move_node_into` for the convinience function makes a lot of sense.
@ -2184,2 +2128,2 @@
array_utils::fill_index_range(reorder_indices.slice(IndexRange(reorder_from + 1, dist)),
reorder_from);
/* Stash the initial layer order that we can refer back to later */
auto old_layer_index_by_layer = Map<const bke::greasepencil::Layer *, int>();
Member

We like to avoid auto when possible. This can just be Map<const bke::greasepencil::Layer *, int> old_layer_index_by_layer;

We like to avoid `auto` when possible. This can just be `Map<const bke::greasepencil::Layer *, int> old_layer_index_by_layer;`
Author
Contributor

Good to know, thanks. You might consider adding that to the style guide, especially since the default linter settings in some IDEs (or at least mine) nudge towards using auto wherever possible.

Good to know, thanks. You might consider adding that to the [style guide](https://wiki.blender.org/wiki/Style_Guide/C_Cpp), especially since the default linter settings in some IDEs (or at least mine) nudge towards using auto wherever possible.
filedescriptor marked this conversation as resolved
@ -2204,0 +2140,4 @@
/* Compose the mapping from old layer indices to new layer indices */
Array<int> new_by_old_map(layers.size());
int i_new = 0;
for (const bke::greasepencil::Layer *layer : layers) {
Member
for (const int layer_i_new : layers.index_range()) {
    const bke::greasepencil::Layer *layer = layers[layer_i_new];
    BLI_assert(old_layer_index_by_layer.contains(layer));
    const int layer_i_old = old_layer_index_by_layer.pop(layer);
    new_by_old_map[layer_i_old] = layer_i_new;
}
``` for (const int layer_i_new : layers.index_range()) { const bke::greasepencil::Layer *layer = layers[layer_i_new]; BLI_assert(old_layer_index_by_layer.contains(layer)); const int layer_i_old = old_layer_index_by_layer.pop(layer); new_by_old_map[layer_i_old] = layer_i_new; } ```
Author
Contributor

Indeed! An embarrassing oversight

Indeed! An embarrassing oversight
filedescriptor marked this conversation as resolved
Member

Bah, I can't figure out how to do comments on multiple lines of code in Gitea, so I'll just ask my questions here:

It's a bit weird but you can click the plus on a line, then "Start Review", add all your comments and then in the upper right where it says "Review", just press the "Comment" button.

  1. Could the implementation of grow_customdata() be simplified even further now that it only ever inserts at the end?

Yes, I left a comment in the code about it.

  1. In my new implementation of reorder_layer_data(), is it too risky to use pointers as map keys? I don't know how likely or unlikely it would be for an an operation that changes the order of the layers to move them around in memory. If that's a concern, all that's really needed is a unique identifier each layer before & after the layer order is changed.

Because the layer tree is implemented with links and linked-lists, I don't think this is a problem. Reordering should only ever change the link pointers and shouldn't reallocate anything. Using the pointers as keys is ok.

> Bah, I can't figure out how to do comments on multiple lines of code in Gitea, so I'll just ask my questions here: It's a bit weird but you can click the plus on a line, then "Start Review", add all your comments and then in the upper right where it says "Review", just press the "Comment" button. > 1. Could the implementation of `grow_customdata()` be simplified even further now that it only ever inserts at the end? Yes, I left a comment in the code about it. > 2. In my new implementation of reorder_layer_data(), is it too risky to use pointers as map keys? I don't know how likely or unlikely it would be for an an operation that changes the order of the layers to move them around in memory. If that's a concern, all that's really needed is a unique identifier each layer before & after the layer order is changed. Because the layer tree is implemented with links and linked-lists, I don't think this is a problem. Reordering should only ever change the link pointers and shouldn't reallocate anything. Using the pointers as keys is ok.
Douglas Paul added 4 commits 2023-10-21 14:57:29 +02:00
81e0201f22 Avoid unnecessarily adding a layer to the non-root group
There was no need to add the new layer to the active layer's group
instead of the root group, because the new layer will be moved to the
correct place via the subsequent call to move_node_after() anyway.
buildbot/vexp-code-patch-coordinator Build done. Details
3bdb22dfea
Refactor so adding a layer to the root group is the typical case
and adding to a specific group is a convenience method
Member

@blender-bot build

@blender-bot build
Douglas Paul reviewed 2023-10-21 16:04:49 +02:00
@ -2121,2 +2073,2 @@
grow_customdata(grease_pencil->layers_data, insertion_index, layers.size());
}
blender::bke::greasepencil::Layer &new_layer = add_layer(name);
move_node_into(new_layer.as_node(), parent_group);
Author
Contributor

If someone happened to call this method and pass in the root group as parent_group, then this call to move_node_into() is just wasted effort. I could add a check to avoid that, but all we'd really be doing is optimizing a case where the calling code was using the wrong method signature (because they should instead use the signature that implicitly adds to the root group).

If someone happened to call this method and pass in the root group as `parent_group`, then this call to `move_node_into()` is just wasted effort. I could add a check to avoid that, but all we'd really be doing is optimizing a case where the calling code was using the wrong method signature (because they should instead use the signature that implicitly adds to the root group).
Member

Yea optimizing a case that the function shouldn't be used for in the first place seems like a waste. Better keep it simple.

Yea optimizing a case that the function shouldn't be used for in the first place seems like a waste. Better keep it simple.
@ -2205,0 +2147,4 @@
BLI_assert(old_layer_index_by_layer.is_empty());
/* Use the mapping to re-order the custom data */
reorder_customdata(grease_pencil.layers_data, new_by_old_map);
Author
Contributor

It's possible that the new layer order is exactly the same as the old layer order, in which case we could skip this call to reorder_customdata(). It's tempting to detect that case and skip this call as an optimization, but I'm not sure there's any practical benefit to doing that? It would arguably arguably just "complexity creep" to optimize things that don't really need to be optimized.

It's possible that the new layer order is exactly the same as the old layer order, in which case we could skip this call to `reorder_customdata()`. It's tempting to detect that case and skip this call as an optimization, but I'm not sure there's any practical benefit to doing that? It would arguably arguably just "complexity creep" to optimize things that don't really need to be optimized.
Member

Agreed. We might come back to this if it is an issue for some reason, but I can't see why it would be a bottleneck right now.

Agreed. We might come back to this if it is an issue for some reason, but I can't see why it would be a bottleneck right now.
Douglas Paul added 1 commit 2023-10-21 16:23:56 +02:00
Falk David approved these changes 2023-10-21 16:27:47 +02:00
Falk David left a comment
Member

Code looks good now :) I'll test this locally once more, but I think we can merge it after that goes well.

Code looks good now :) I'll test this locally once more, but I think we can merge it after that goes well.
Falk David merged commit f5c9acc154 into main 2023-10-21 16:47:25 +02:00
Sign in to join this conversation.
No reviewers
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#113962
No description provided.