Sequencer: Avoid storing un-tracked pointers in blend files

Currently, sequencer structs contain several pointers to data within
other structs. These pointers need to be remapped as the structs are
reallocated when reading from blend files. That has worked so far
because the pointers are exactly the values from the Blender session
that saved the file. With the implementation of #127706, the pointers
in the file aren't "real" anymore, and we can't offset them to get the
struct that contained the data. I'm working on the pointer stability
solution now to address a memfile undo performance regression
in 5.0 due to the `AttributeStorage` read/write design.

This commit replaces these 4 mid-struct pointers to point to the
containing strips instead, and uses some trivial logic to access the
fallback root sequence channels and strips. This makes the pointer
remapping on file load possible again.

This change is backward compatible but not forward compatible.

Second try after #144626

Pull Request: https://projects.blender.org/blender/blender/pulls/144878
This commit is contained in:
Hans Goudey
2025-08-29 16:58:08 +02:00
committed by Hans Goudey
parent a2614464e5
commit ae53dc3a60
15 changed files with 73 additions and 167 deletions

View File

@@ -329,7 +329,6 @@ static void scene_copy_data(Main *bmain,
/* Copy sequencer, this is local data! */
if (scene_src->ed) {
scene_dst->ed = MEM_callocN<Editing>(__func__);
scene_dst->ed->seqbasep = &scene_dst->ed->seqbase;
scene_dst->ed->cache_flag = scene_src->ed->cache_flag;
scene_dst->ed->show_missing_media_flag = scene_src->ed->show_missing_media_flag;
scene_dst->ed->proxy_storage = scene_src->ed->proxy_storage;
@@ -342,7 +341,6 @@ static void scene_copy_data(Main *bmain,
blender::seq::StripDuplicate::All,
flag_subdata);
BLI_duplicatelist(&scene_dst->ed->channels, &scene_src->ed->channels);
scene_dst->ed->displayed_channels = &scene_dst->ed->channels;
}
if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0) {
@@ -1353,14 +1351,13 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
}
if (sce->ed) {
ListBase *old_seqbasep = &sce->ed->seqbase;
ListBase *old_displayed_channels = &sce->ed->channels;
BLO_read_struct(reader, Editing, &sce->ed);
Editing *ed = sce->ed;
ed->act_strip = static_cast<Strip *>(
BLO_read_get_new_data_address_no_us(reader, ed->act_strip, sizeof(Strip)));
ed->current_meta_strip = static_cast<Strip *>(
BLO_read_get_new_data_address_no_us(reader, ed->current_meta_strip, sizeof(Strip)));
ed->prefetch_job = nullptr;
ed->runtime.strip_lookup = nullptr;
ed->runtime.media_presence = nullptr;
@@ -1377,88 +1374,14 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
blender::seq::blend_read(reader, &ed->seqbase);
BLO_read_struct_list(reader, SeqTimelineChannel, &ed->channels);
/* link metastack, slight abuse of structs here,
* have to restore pointer to internal part in struct */
{
const int seqbase_offset_file = BLO_read_struct_member_offset(
reader, "Strip", "ListBase", "seqbase");
const int channels_offset_file = BLO_read_struct_member_offset(
reader, "Strip", "ListBase", "channels");
const size_t seqbase_offset_mem = offsetof(Strip, seqbase);
const size_t channels_offset_mem = offsetof(Strip, channels);
/* stack */
BLO_read_struct_list(reader, MetaStack, &(ed->metastack));
/* seqbase root pointer */
if (ed->seqbasep == old_seqbasep || seqbase_offset_file < 0) {
ed->seqbasep = &ed->seqbase;
}
else {
void *seqbase_poin = POINTER_OFFSET(ed->seqbasep, -seqbase_offset_file);
LISTBASE_FOREACH (MetaStack *, ms, &ed->metastack) {
BLO_read_struct(reader, Strip, &ms->parent_strip);
seqbase_poin = BLO_read_get_new_data_address_no_us(reader, seqbase_poin, sizeof(Strip));
if (seqbase_poin) {
ed->seqbasep = (ListBase *)POINTER_OFFSET(seqbase_poin, seqbase_offset_mem);
}
else {
ed->seqbasep = &ed->seqbase;
}
}
/* Active channels root pointer. */
if (ELEM(ed->displayed_channels, old_displayed_channels, nullptr) ||
channels_offset_file < 0)
{
ed->displayed_channels = &ed->channels;
}
else {
void *channels_poin = POINTER_OFFSET(ed->displayed_channels, -channels_offset_file);
channels_poin = BLO_read_get_new_data_address_no_us(
reader, channels_poin, sizeof(SeqTimelineChannel));
if (channels_poin) {
ed->displayed_channels = (ListBase *)POINTER_OFFSET(channels_poin, channels_offset_mem);
}
else {
ed->displayed_channels = &ed->channels;
}
}
/* stack */
BLO_read_struct_list(reader, MetaStack, &(ed->metastack));
LISTBASE_FOREACH (MetaStack *, ms, &ed->metastack) {
BLO_read_struct(reader, Strip, &ms->parent_strip);
if (ms->oldbasep == old_seqbasep || seqbase_offset_file < 0) {
ms->oldbasep = &ed->seqbase;
}
else {
void *seqbase_poin = POINTER_OFFSET(ms->oldbasep, -seqbase_offset_file);
seqbase_poin = BLO_read_get_new_data_address_no_us(reader, seqbase_poin, sizeof(Strip));
if (seqbase_poin) {
ms->oldbasep = (ListBase *)POINTER_OFFSET(seqbase_poin, seqbase_offset_mem);
}
else {
ms->oldbasep = &ed->seqbase;
}
}
if (ELEM(ms->old_channels, old_displayed_channels, nullptr) || channels_offset_file < 0) {
ms->old_channels = &ed->channels;
}
else {
void *channels_poin = POINTER_OFFSET(ms->old_channels, -channels_offset_file);
channels_poin = BLO_read_get_new_data_address_no_us(
reader, channels_poin, sizeof(SeqTimelineChannel));
if (channels_poin) {
ms->old_channels = (ListBase *)POINTER_OFFSET(channels_poin, channels_offset_mem);
}
else {
ms->old_channels = &ed->channels;
}
}
}
ms->old_strip = static_cast<Strip *>(
BLO_read_get_new_data_address_no_us(reader, ms->old_strip, sizeof(Strip)));
}
}

View File

@@ -377,11 +377,6 @@ void BLO_read_glob_list(BlendDataReader *reader, ListBase *list);
BlendFileReadReport *BLO_read_data_reports(BlendDataReader *reader);
struct Library *BLO_read_data_current_library(BlendDataReader *reader);
int BLO_read_struct_member_offset(const BlendDataReader *reader,
const char *stype,
const char *vartype,
const char *name);
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -5224,14 +5224,6 @@ int BLO_read_fileversion_get(BlendDataReader *reader)
return reader->fd->fileversion;
}
int BLO_read_struct_member_offset(const BlendDataReader *reader,
const char *stype,
const char *vartype,
const char *name)
{
return DNA_struct_member_offset_by_name_with_alias(reader->fd->filesdna, stype, vartype, name);
}
void BLO_read_struct_list_with_size(BlendDataReader *reader,
const size_t expected_elem_size,
ListBase *list)

View File

@@ -378,7 +378,8 @@ static void seq_update_meta_disp_range(Scene *scene)
blender::seq::time_right_handle_frame_set(scene, ms->parent_strip, ms->disp_range[1]);
/* Recalculate effects using meta strip. */
LISTBASE_FOREACH (Strip *, strip, ms->oldbasep) {
ListBase *old_seqbasep = ms->old_strip ? &ms->old_strip->seqbase : &ed->seqbase;
LISTBASE_FOREACH (Strip *, strip, old_seqbasep) {
if (strip->input2) {
strip->start = strip->startdisp = max_ii(strip->input1->startdisp,
strip->input2->startdisp);
@@ -386,9 +387,8 @@ static void seq_update_meta_disp_range(Scene *scene)
}
}
/* Ensure that active seqbase points to active meta strip seqbase. */
MetaStack *active_ms = blender::seq::meta_stack_active_get(ed);
blender::seq::active_seqbase_set(ed, &active_ms->parent_strip->seqbase);
active_ms->old_strip = ms->parent_strip;
}
}

View File

@@ -3424,16 +3424,6 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
}
blender::seq::channels_ensure(&ed->channels);
blender::seq::for_each_callback(&scene->ed->seqbase, strip_meta_channels_ensure, nullptr);
ed->displayed_channels = &ed->channels;
ListBase *previous_channels = &ed->channels;
LISTBASE_FOREACH (MetaStack *, ms, &ed->metastack) {
ms->old_channels = previous_channels;
previous_channels = &ms->parent_strip->channels;
/* If `MetaStack` exists, active channels must point to last link. */
ed->displayed_channels = &ms->parent_strip->channels;
}
}
}

View File

@@ -2126,6 +2126,20 @@ static void remove_in_and_out_node_interface(bNodeTree &node_tree)
remove_in_and_out_node_panel_recursive(node_tree.tree_interface.root_panel);
}
static void sequencer_remove_listbase_pointers(Scene &scene)
{
Editing *ed = scene.ed;
if (!ed) {
return;
}
const MetaStack *last_meta_stack = blender::seq::meta_stack_active_get(ed);
if (!last_meta_stack) {
return;
}
ed->current_meta_strip = last_meta_stack->parent_strip;
blender::seq::meta_stack_set(&scene, last_meta_stack->parent_strip);
}
void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
{
using namespace blender;
@@ -2882,6 +2896,12 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 68)) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
sequencer_remove_listbase_pointers(*scene);
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -167,7 +167,6 @@ static bool sequencer_write_copy_paste_file(Main *bmain_src,
/* Create an empty sequence editor data to store all copied strips. */
scene_dst->ed = MEM_callocN<Editing>(__func__);
scene_dst->ed->seqbasep = &scene_dst->ed->seqbase;
seq::seqbase_duplicate_recursive(bmain_src,
scene_src,
scene_dst,
@@ -177,7 +176,6 @@ static bool sequencer_write_copy_paste_file(Main *bmain_src,
0);
BLI_duplicatelist(&scene_dst->ed->channels, &scene_src->ed->channels);
scene_dst->ed->displayed_channels = &scene_dst->ed->channels;
/* Save current frame and active strip. */
scene_dst->r.cfra = scene_src->r.cfra;
@@ -499,7 +497,7 @@ wmOperatorStatus sequencer_clipboard_paste_exec(bContext *C, wmOperator *op)
BKE_id_delete(bmain_dst, scene_src);
Strip *iseq_first = static_cast<Strip *>(nseqbase.first);
BLI_movelisttolist(ed_dst->seqbasep, &nseqbase);
BLI_movelisttolist(ed_dst->current_strips(), &nseqbase);
/* Restore "first" pointer as BLI_movelisttolist sets it to nullptr */
nseqbase.first = iseq_first;
@@ -517,8 +515,8 @@ wmOperatorStatus sequencer_clipboard_paste_exec(bContext *C, wmOperator *op)
* strip. */
seq::transform_translate_strip(scene_dst, istrip, ofs);
/* Ensure, that pasted strips don't overlap. */
if (seq::transform_test_overlap(scene_dst, ed_dst->seqbasep, istrip)) {
seq::transform_seqbase_shuffle(ed_dst->seqbasep, istrip, scene_dst);
if (seq::transform_test_overlap(scene_dst, ed_dst->current_strips(), istrip)) {
seq::transform_seqbase_shuffle(ed_dst->current_strips(), istrip, scene_dst);
}
}

View File

@@ -1360,7 +1360,7 @@ static void preview_draw_all_image_overlays(const bContext *C,
{
ListBase *channels = seq::channels_displayed_get(&editing);
VectorSet strips = seq::query_rendered_strips(
scene, channels, editing.seqbasep, timeline_frame, 0);
scene, channels, editing.current_strips(), timeline_frame, 0);
Strip *active_seq = seq::select_active_get(scene);
for (Strip *strip : strips) {
/* TODO(sergey): Avoid having per-strip strip-independent checks. */

View File

@@ -305,8 +305,11 @@ typedef struct Strip {
typedef struct MetaStack {
struct MetaStack *next, *prev;
ListBase *oldbasep;
ListBase *old_channels;
/**
* The meta-strip that contains `parent_strip`. May be null (that means it is the top-most
* strips).
*/
Strip *old_strip;
Strip *parent_strip;
/* The startdisp/enddisp when entering the metastrip. */
int disp_range[2];
@@ -336,16 +339,11 @@ typedef struct EditingRuntime {
typedef struct Editing {
/**
* Pointer to the current list of strips being edited (can be within a meta-strip).
* \note Use #current_strips() to access, rather than using this variable directly.
* The current meta-strip being edited and/or viewed, may be null, in which case the top-most
* strips are used.
*/
ListBase *seqbasep;
/**
* Pointer to the current list of channels being displayed (can be within a meta-strip).
* \note Use #current_channels() to access, rather than using this variable directly.
*/
ListBase *displayed_channels;
void *_pad0;
Strip *current_meta_strip;
/** Pointer to the top-most strips. */
ListBase seqbase;
ListBase metastack;

View File

@@ -15,8 +15,8 @@ struct Strip;
namespace blender::seq {
/** The active displayed channels list, either from the root sequence or from a meta-strip. */
ListBase *channels_displayed_get(const Editing *ed);
void channels_displayed_set(Editing *ed, ListBase *channels);
void channels_ensure(ListBase *channels);
void channels_duplicate(ListBase *channels_dst, ListBase *channels_src);
void channels_free(ListBase *channels);

View File

@@ -69,13 +69,6 @@ void editing_free(Scene *scene, bool do_id_user);
* \return pointer to active seqbase. returns NULL if ed is NULL
*/
ListBase *active_seqbase_get(const Editing *ed);
/**
* Set seqbase that is being viewed currently. This can be main seqbase or meta strip seqbase
*
* \param ed: sequence editor data
* \param seqbase: ListBase with strips
*/
void active_seqbase_set(Editing *ed, ListBase *seqbase);
Strip *strip_alloc(ListBase *lb, int timeline_frame, int channel, int type);
void strip_free(Scene *scene, Strip *strip);
/**

View File

@@ -30,11 +30,6 @@ ListBase *channels_displayed_get(const Editing *ed)
return ed ? ed->current_channels() : nullptr;
}
void channels_displayed_set(Editing *ed, ListBase *channels)
{
ed->displayed_channels = channels;
}
void channels_ensure(ListBase *channels)
{
/* Allocate channels. Channel 0 is never used, but allocated to prevent off by 1 issues. */

View File

@@ -372,10 +372,10 @@ static void seq_prefetch_update_active_seqbase(PrefetchJob *pfjob)
if (ms_orig != nullptr) {
Strip *meta_eval = original_strip_get(ms_orig->parent_strip, pfjob->scene_eval);
active_seqbase_set(ed_eval, &meta_eval->seqbase);
ed_eval->current_meta_strip = meta_eval;
}
else {
active_seqbase_set(ed_eval, &ed_eval->seqbase);
ed_eval->current_meta_strip = nullptr;
}
}

View File

@@ -1985,8 +1985,9 @@ ImBuf *render_give_ibuf(const RenderData *context, float timeline_frame, int cha
if ((chanshown < 0) && !BLI_listbase_is_empty(&ed->metastack)) {
int count = BLI_listbase_count(&ed->metastack);
count = max_ii(count + chanshown, 0);
seqbasep = ((MetaStack *)BLI_findlink(&ed->metastack, count))->oldbasep;
channels = ((MetaStack *)BLI_findlink(&ed->metastack, count))->old_channels;
MetaStack *ms = static_cast<MetaStack *>(BLI_findlink(&ed->metastack, count));
seqbasep = &ms->old_strip->seqbase;
channels = &ms->old_strip->channels;
chanshown = 0;
}
else {

View File

@@ -284,11 +284,9 @@ Editing *editing_ensure(Scene *scene)
Editing *ed;
ed = scene->ed = MEM_callocN<Editing>("addseq");
ed->seqbasep = &ed->seqbase;
ed->cache_flag = (SEQ_CACHE_PREFETCH_ENABLE | SEQ_CACHE_STORE_FINAL_OUT | SEQ_CACHE_STORE_RAW);
ed->show_missing_media_flag = SEQ_EDIT_SHOW_MISSING_MEDIA;
ed->displayed_channels = &ed->channels;
channels_ensure(ed->displayed_channels);
channels_ensure(&ed->channels);
}
return scene->ed;
@@ -427,11 +425,6 @@ ListBase *active_seqbase_get(const Editing *ed)
return ed ? ed->current_strips() : nullptr;
}
void active_seqbase_set(Editing *ed, ListBase *seqbase)
{
ed->seqbasep = seqbase;
}
static MetaStack *seq_meta_stack_alloc(const Scene *scene, Strip *strip_meta)
{
Editing *ed = editing_get(scene);
@@ -441,9 +434,7 @@ static MetaStack *seq_meta_stack_alloc(const Scene *scene, Strip *strip_meta)
ms->parent_strip = strip_meta;
/* Reference to previously displayed timeline data. */
Strip *higher_level_meta = lookup_meta_by_strip(ed, strip_meta);
ms->oldbasep = higher_level_meta ? &higher_level_meta->seqbase : &ed->seqbase;
ms->old_channels = higher_level_meta ? &higher_level_meta->channels : &ed->channels;
ms->old_strip = lookup_meta_by_strip(ed, strip_meta);
ms->disp_range[0] = time_left_handle_frame_get(scene, ms->parent_strip);
ms->disp_range[1] = time_right_handle_frame_get(scene, ms->parent_strip);
@@ -473,13 +464,10 @@ void meta_stack_set(const Scene *scene, Strip *dst)
seq_meta_stack_alloc(scene, meta_parent);
}
active_seqbase_set(ed, &dst->seqbase);
channels_displayed_set(ed, &dst->channels);
ed->current_meta_strip = dst;
}
else {
/* Go to top level, exiting meta strip. */
active_seqbase_set(ed, &ed->seqbase);
channels_displayed_set(ed, &ed->channels);
ed->current_meta_strip = nullptr;
}
}
@@ -487,8 +475,7 @@ Strip *meta_stack_pop(Editing *ed)
{
MetaStack *ms = meta_stack_active_get(ed);
Strip *meta_parent = ms->parent_strip;
active_seqbase_set(ed, ms->oldbasep);
channels_displayed_set(ed, ms->old_channels);
ed->current_meta_strip = ms->old_strip;
BLI_remlink(&ed->metastack, ms);
MEM_freeN(ms);
return meta_parent;
@@ -1159,20 +1146,34 @@ void eval_strips(Depsgraph *depsgraph, Scene *scene, ListBase *seqbase)
ListBase *Editing::current_strips()
{
return this->seqbasep;
if (this->current_meta_strip) {
return &this->current_meta_strip->seqbase;
}
return &this->seqbase;
}
ListBase *Editing::current_strips() const
{
if (this->current_meta_strip) {
return &this->current_meta_strip->seqbase;
}
/* NOTE: Const correctness is non-existent with ListBase anyway. */
return this->seqbasep;
return &const_cast<ListBase &>(this->seqbase);
}
ListBase *Editing::current_channels()
{
return this->displayed_channels;
if (this->current_meta_strip) {
return &this->current_meta_strip->channels;
}
return &this->channels;
}
ListBase *Editing::current_channels() const
{
if (this->current_meta_strip) {
return &this->current_meta_strip->channels;
}
/* NOTE: Const correctness is non-existent with ListBase anyway. */
return this->displayed_channels;
return &const_cast<ListBase &>(this->channels);
}