VSE: use c++ container for strip iterator #111909
No reviewers
Labels
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
4 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#111909
Loading…
Reference in New Issue
No description provided.
Delete Branch "iss/blender:iterator-cpp"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Idea is to use blender::VectorSet instead of
SeqCollection
struct. This makes it possible to use native for loops, soSEQ_ITERATOR_FOREACH
macro can be removed.Another feature is, sets of strips no longer needs to be freed. However this poses a limitation, that query functions can not be used in case where these sets need to be available outside of scope where they are created.
[WIP} VSE use c++ conteiner for strip iteratorto [WIP] VSE use c++ conteiner for strip iterator[WIP] VSE use c++ conteiner for strip iteratorto WIP: VSE: use c++ container for strip iteratorJust looking at this out of curiousity-- it's generally preferred to use references instead of pointers for arguments that aren't expected to be null. For example,
VectorSet<Sequence *> *strips_dst
should becomeVectorSet<Sequence *> &strips_dst
. That helps simplify using the argument in the function.@HooglyBoogly Thanks for feedback, I was thinking actually about involving you in review. if you would like to do it, feel free to assign yourself.
Will have to read about reference vs. pointer. I am still very new to C++.
As I am reading about references, there are few places where I rely on returning
nullptr
instead of pointer toVector
orVectorSet
. I remember struggling a bit to refine logic so this can be avoided, but I may keep using pointers in such case.How about returning an empty container? That can be done with just
return {};
Maybe it doesn't work, but maybe it does?One place I think is in lookup cache, where I gather effects of a strip, so empty container could mean no effects, but
nullptr
means , that cache is not initialized.Now this cache is built for all strips at once, so I can assume empty container means no effects, but it was an example of how this could be an issue.
In any case, will change pointers to references and report on any issues I found.
@HooglyBoogly I have started to replace pointers with references, and I have identified 2 obstacles:
First is keeping object available in some context struct:
I can't use reference there, since I need
c
to exist until I decide to free it.Second obstacle is above mentioned lookup cache for effects:
This cache is
blender::Map<const Sequence *, blender::VectorSet<Sequence *>>
TheVectorSet
contains effect strips ofMap
s key which is input strip. This cache is designed, so it iterates over strips once.But since strips can have many effects and
ListBase
is not ordered, I would have to iterate twice to create VectorSets first and then add effects to them in second iteration. Also this would result in unnecessary memory allocations.So far I have been able to resolve this by using pointers internally and returning reference. So far to return empty set, I have declared global
empty_set
object. I guess there is some magic string to cast it inline, but I wasn't able to do it.Why don't you use smart pointers and constructors? What about optional return values?
I am not sure how smart pointers would help here. I need object to be allocated on heap anyway. I can use it if that is preffered to plain pointers.
I looked at node code where similar case is handled, and I think, that the answer is
MEM_new()
function, which can initialize non-trivial objects.Turns out, that I wasn't supposed to return reference to object, then
return {}
worked.However there are few places where empty set is supplied to
SEQ_transform_handle_overlap()
as argument. So far I doBut I can make variant of this function where it does not use this argument, since I know in advance, that the set will be empty. That is much nicer solution.
WIP: VSE: use c++ container for strip iteratorto VSE: use c++ container for strip iteratorNice to remove these single-purpose data structures that are unnecessary in C++ code! A few of my comments apply everywhere in the patch, particularly using
Span
where possible and avoidingauto
.@ -113,3 +113,2 @@
int best_distance = MAXFRAME;
Sequence *seq;
SEQ_ITERATOR_FOREACH (seq, strips) {
for (auto seq : strips) {
Usually we don't use
auto
for cases like this, since the type name is helpful to the reader and clarifies what you can do with the variable.@ -780,3 +778,3 @@
}
static void seq_build_proxy(bContext *C, SeqCollection *movie_strips)
static void seq_build_proxy(bContext *C, blender::VectorSet<Sequence *> &movie_strips)
Functions like this should take a
Span
so that they aren't dependent on which container the data is actually stored in.Same for any other function in this PR that doesn't modify or take ownership of the set.
@ -885,3 +878,3 @@
const bool use_sync_markers = (((SpaceSeq *)area->spacedata.first)->flag &
SEQ_MARKER_TRANS) != 0;
SEQ_transform_handle_overlap(scene, ed->seqbasep, strip_col, nullptr, use_sync_markers);
{
It's not clear why the braces are added here.
@ -2249,3 +2247,3 @@
case SEQ_SIDE_RIGHT:
if (SEQ_time_left_handle_frame_get(scene, seq) >=
SEQ_time_right_handle_frame_get(scene, test)) {
SEQ_time_right_handle_frame_get(scene, test))
Looks like you're using the wrong clang format version?
@ -164,3 +161,3 @@
* \return collection of strips (`Sequence`)
*/
struct SeqCollection *all_strips_from_context(struct bContext *C);
blender::VectorSet<Sequence *> all_strips_from_context(struct bContext *C);
I moved this header to C++ in main, that way this PR doesn't need to remove the
extern "C"
itself.@ -67,0 +63,4 @@
strips.add(seq);
blender::VectorSet<Sequence *> dependant;
SEQ_iterator_set_expand(scene, seqbase, strips, SEQ_query_strip_effect_chain);
dependant.remove(0);
remove(0)
seems suspect, since0
is not a pointer. What's the goal there?Eeh, this seems to be incorrect even in original code.
The goal is to have 2 sets of strips -
strips
can be transformed freely,dependant
are "children" of strips instrips
set and can be transformed in Y direction only.SEQ_transform_handle_overlap()
then does the magic according to these restrictions.@ -328,1 +327,3 @@
SEQ_filter_selected_strips(strips);
blender::VectorSet strips = SEQ_query_rendered_strips(
scene, channels, seqbase, scene->r.cfra, 0);
strips.remove_if([&](auto seq) { return (seq->flag & SELECT) == 0; });
I realize this is just porting the existing code, it does seem a bit inefficient to build a container of all the strips and then just filter it to the selected ones. It might be just one strip selected with hundreds visible for example. Feel free to ignore this for later though!
SEQ_query_rendered_strips()
does not return all strips. It returns strips that will be rendered at particular frame.Without some form of caching, this is IMO most efficient way to get the result I need.
My point is, doing the filter selection inside of
SEQ_query_rendered_strips
would be more efficient, since we don't build a largeVectorSet
just to remove most of the items. As long as we're agreed on that, I think both options are valid-- there's an argument to be made about code clarity too.Not sure if I understand, you mean to move filtering of selected items into function
SEQ_query_rendered_strips
? I can't do that, because that function is used in bunch of places where it needs to provide unselected strips as well.I mean as an option, not all the time. But anyway, just want us to be clear on the tradeoff.
I understand what you mean and I started to think about having one query function, that you can specify a bunch of pre-defined conditions. But ultimately I think it would be harder to read the code and understand what the function is supposed to do. But at the same time perhaps closer to my original goal. But after some time I started to more appreciate just having a container I can fill as I want to, than having a library of calls to do one predefined thing.
@ -216,0 +224,4 @@
}
}
typedef struct SequencesAllIterator {
typedef
is unnecessary in this situation in C++@ -143,2 +65,3 @@
void SEQ_collection_exclude(SeqCollection *collection, SeqCollection *exclude_elements)
// This should be renamed if it is still needed.
void SEQ_iterator_set_merge(VectorSet<Sequence *> &strips_dst, VectorSet<Sequence *> strips_src)
I'd suggest removing this and replacing its uses with
strips_dst.add_multiple(strips_src);
Ah nice, I wanted to suggest this feature, but it actually exists.
@ -30,3 +33,3 @@
GHash *seq_by_name;
GHash *meta_by_seq;
GHash *effects_by_seq;
blender::Map<const Sequence *, blender::VectorSet<Sequence *> *> *effects_by_seq;
The VectorSet here shouldn't need to be a pointer. If it does need to be a pointer, it should probably use
std::unique_ptr
. We try to avoid rawnew
anddelete
whenever possible (alsoMEM_new
andMEM_delete
too), since that sort of manual memory management is ideally unnecessary in C++.I tried to use
std::unique_ptr
, but compiler had some issues aroundlookup_default()
. In any case, does the smart pointer know, that the object is still used when it is added toMap
?I think you'll have to be more specific about what the issues were
Wanted to check on this tomorrow more in detail, but what I did was (on top of current patch):
I have looked bit more into this, and I am affraid I won't be able to resolve this issue. The compiler says something like "Attempted to reference removed function" probably when compiling
lookup->effects_by_seq->lookup_default(input, nullptr)
@ -112,3 +115,3 @@
(*lookup)->meta_by_seq = nullptr;
(*lookup)->effects_by_seq = nullptr;
//(*lookup)->effects_by_seq = nullptr;
MEM_freeN(*lookup);
SequenceLookup
is now a non-trivial struct, and allocating it needs to be done withMEM_new
, and freeing withMEM_delete
(or smart pointers, etc).@ -171,3 +174,3 @@
}
SeqCollection *seq_sequence_lookup_effects_by_seq(const Scene *scene, const Sequence *key)
blender::VectorSet<Sequence *> seq_sequence_lookup_effects_by_seq(const Scene *scene,
This returns a copy of the container stored in the map. Maybe it can return a span instead and just reference the data in the map?
Just the change in data structures is really nice here. It all becomes much more obvious, nice job.
All headers affected by this cleanup that now have templates in them should be moved to
.hh
and have theirextern "C"
blocks removed in a separate commit that lands in main before this. That way there is no half-way situation where we're using C++ features in a C header.@ -108,3 +108,3 @@
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
SeqCollection *strips = SEQ_query_all_strips(seqbase);
blender::Span<Sequence *> strips = SEQ_query_all_strips(seqbase);
Did you test this? I wouldn't expect this to work.
The
VectorSet
returned bySEQ_query_all_strips
needs to be stored in a variable so it's not immediately destructed.The
Span
will fill itself with the memory in theVectorSet
, but crucially it doesn't own the memory, which will be freed with theVectorSet
.Spans are non-owning references to memory from elsewhere. In C++ (and C too) it's the programmer's responsibility to think about the lifetime and ownership of data to avoid this problem.
I guess this could be compiler dependent behavior. It works here, but will play it safe.
I'm fairly certain it's undefined behavior in every compiler. it's not "playing it safe" to change this, just fixing broken code.
@ -834,3 +826,1 @@
if (overlap_shuffle_override) {
strip_col = SEQ_collection_create(__func__);
}
blender::VectorSet<Sequence *> added_strips;
It's clearer to only use the
VectorSet
if you actually need de-duplication Since this only gets two items added to it, I think it's not necessary here, so a plainVector
is probably a better choice.@ -971,3 +958,3 @@
}
if (SEQ_collection_len(movie_strips) == 0) {
if (movie_strips.size() == 0) {
.is_empty()
should always be used instead of checking if the size is zero@ -154,3 +154,3 @@
* \return collection of strips (`Sequence`)
*/
SeqCollection *all_strips_from_context(bContext *C);
blender::VectorSet<Sequence *> all_strips_from_context(struct bContext *C);
No need to add
struct
back in the definition hereI never understood the logic when I need to add
struct
to declarations... Sometime compiler complains, sometimes it doesn't, so I add it everywhere :/@ -164,3 +164,3 @@
* \return collection of strips (`Sequence`)
*/
SeqCollection *selected_strips_from_context(bContext *C);
blender::VectorSet<Sequence *> selected_strips_from_context(struct bContext *C);
Same here
@ -223,3 +222,3 @@
static int retiming_key_add_from_selection(bContext *C,
wmOperator *op,
SeqCollection *strips,
blender::VectorSet<Sequence *> &strips,
Pass this as a
Span<Sequence *>
if you don't have to add items.@ -108,3 +104,3 @@
BLI_ghash_free((*lookup)->seq_by_name, nullptr, nullptr);
BLI_ghash_free((*lookup)->meta_by_seq, nullptr, nullptr);
BLI_ghash_free((*lookup)->effects_by_seq, nullptr, SEQ_collection_free_void_p);
// BLI_ghash_free((*lookup)->effects_by_seq, nullptr, nullptr);
What's the status of this commented code? Looks as if the change is incomplete here?
Forgot to remove.
Like I mentioned above, the C++ conversion of the headers (removing extern "C") and changing to .hh should be committed to main separately outside of this review. The change makes the PR too big and difficult to review properly, and combines one large trivial change with a more complex one.
@blender-bot build
Great, thanks!
@blender-bot build
@blender-bot build
For the record, in the future when making such huge refactor, it would be good to do some basic trivial testing in affected code...
This broke something as basic as duplicating four strips together, due to a small mistake (see
3fccfe0bc6
).Also, it would be better to split such changes in more, smaller commits too. Makes it easier to bisect and understand what is the source of the problem.
And
869372ffc3
fixes another... surprising mistake in that PR :(@mont29 Thanks for fixes, that
static
qualifier was really stupid, not sure what happened there.For splitting the patch, I guess I could have done multiple commits for bunch of files, but this is single functional change so it would be a bit tricky.