Blender V5.0
undofile.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2004 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cstdio>
10#include <cstdlib>
11#include <cstring>
12#include <fcntl.h>
13
14/* open/close */
15#ifndef _WIN32
16# include <unistd.h>
17#else
18# include <io.h>
19#endif
20
21#include "MEM_guardedalloc.h"
22
23#include "DNA_listBase.h"
24
26
27#include "BLO_readfile.hh"
28#include "BLO_undofile.hh"
29
30#include "BKE_lib_id.hh"
31#include "BKE_main.hh"
32#include "BKE_undo_system.hh"
33
34#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
35
36/* **************** support for memory-write, for undo buffers *************** */
37
39{
40 while (MemFileChunk *chunk = static_cast<MemFileChunk *>(BLI_pophead(&memfile->chunks))) {
41 if (chunk->is_identical == false) {
42 MEM_freeN(chunk->buf);
43 }
44 MEM_freeN(chunk);
45 }
46 MEM_delete(memfile->shared_storage);
47 memfile->shared_storage = nullptr;
48 memfile->size = 0;
49}
50
52{
54 /* Removing the user makes sure shared data is freed when the undo step was its last owner. */
55 data.sharing_info->remove_user_and_delete_if_last();
56 }
57}
58
59void BLO_memfile_merge(MemFile *first, MemFile *second)
60{
61 /* We use this mapping to store the memory buffers from second memfile chunks which are not owned
62 * by it (i.e. shared with some previous memory steps). */
63 blender::Map<const char *, MemFileChunk *> buffer_to_second_memchunk;
64
65 /* First, detect all memchunks in second memfile that are not owned by it. */
66 LISTBASE_FOREACH (MemFileChunk *, sc, &second->chunks) {
67 if (sc->is_identical) {
68 buffer_to_second_memchunk.add(sc->buf, sc);
69 }
70 }
71
72 /* Now, check all chunks from first memfile (the one we are removing), and if a memchunk owned by
73 * it is also used by the second memfile, transfer the ownership. */
74 LISTBASE_FOREACH (MemFileChunk *, fc, &first->chunks) {
75 if (!fc->is_identical) {
76 if (MemFileChunk *sc = buffer_to_second_memchunk.lookup_default(fc->buf, nullptr)) {
77 BLI_assert(sc->is_identical);
78 sc->is_identical = false;
79 fc->is_identical = true;
80 }
81 /* Note that if the second memfile does not use that chunk, we assume that the first one
82 * fully owns it without sharing it with any other memfile, and hence it should be freed with
83 * it. */
84 }
85 }
86
87 BLO_memfile_free(first);
88}
89
91{
92 LISTBASE_FOREACH (MemFileChunk *, chunk, &memfile->chunks) {
93 chunk->is_identical_future = false;
94 }
95}
96
98 MemFile *written_memfile,
99 MemFile *reference_memfile)
100{
101 mem_data->written_memfile = written_memfile;
102 mem_data->reference_memfile = reference_memfile;
103 mem_data->reference_current_chunk = reference_memfile ? static_cast<MemFileChunk *>(
104 reference_memfile->chunks.first) :
105 nullptr;
106
107 /* If we have a reference memfile, we generate a mapping between the session_uid's of the
108 * IDs stored in that previous undo step, and its first matching memchunk. This will allow
109 * us to easily find the existing undo memory storage of IDs even when some re-ordering in
110 * current Main data-base broke the order matching with the memchunks from previous step.
111 */
112 if (reference_memfile != nullptr) {
113 uint current_session_uid = MAIN_ID_SESSION_UID_UNSET;
114 LISTBASE_FOREACH (MemFileChunk *, mem_chunk, &reference_memfile->chunks) {
115 if (!ELEM(mem_chunk->id_session_uid, MAIN_ID_SESSION_UID_UNSET, current_session_uid)) {
116 current_session_uid = mem_chunk->id_session_uid;
117 mem_data->id_session_uid_mapping.add_new(current_session_uid, mem_chunk);
118 }
119 }
120 }
121}
122
127
128void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
129{
130 MemFile *memfile = mem_data->written_memfile;
131 MemFileChunk **compchunk_step = &mem_data->reference_current_chunk;
132
133 MemFileChunk *curchunk = MEM_mallocN<MemFileChunk>("MemFileChunk");
134 curchunk->size = size;
135 curchunk->buf = nullptr;
136 curchunk->is_identical = false;
137 /* This is unsafe in the sense that an app handler or other code that does not
138 * perform an undo push may make changes after the last undo push that
139 * will then not be undo. Though it's not entirely clear that is wrong behavior. */
140 curchunk->is_identical_future = true;
141 curchunk->id_session_uid = mem_data->current_id_session_uid;
142 BLI_addtail(&memfile->chunks, curchunk);
143
144 /* we compare compchunk with buf */
145 if (*compchunk_step != nullptr) {
146 MemFileChunk *compchunk = *compchunk_step;
147 if (compchunk->size == curchunk->size) {
148 if (memcmp(compchunk->buf, buf, size) == 0) {
149 curchunk->buf = compchunk->buf;
150 curchunk->is_identical = true;
151 compchunk->is_identical_future = true;
152 }
153 }
154 *compchunk_step = static_cast<MemFileChunk *>(compchunk->next);
155 }
156
157 /* not equal... */
158 if (curchunk->buf == nullptr) {
159 char *buf_new = MEM_malloc_arrayN<char>(size, "Chunk buffer");
160 memcpy(buf_new, buf, size);
161 curchunk->buf = buf_new;
162 memfile->size += size;
163 }
164}
165
166Main *BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene)
167{
168 Main *bmain_undo = nullptr;
169 BlendFileReadParams read_params{};
171 bmain, BKE_main_blendfile_path(bmain), memfile, &read_params, nullptr);
172
173 if (bfd) {
174 bmain_undo = bfd->main;
175 if (r_scene) {
176 *r_scene = bfd->curscene;
177 }
178
179 MEM_delete(bfd);
180 }
181
182 return bmain_undo;
183}
184
185static int64_t undo_read(FileReader *reader, void *buffer, size_t size)
186{
187 UndoReader *undo = (UndoReader *)reader;
188
189 static size_t seek = SIZE_MAX; /* The current position. */
190 static size_t offset = 0; /* Size of previous chunks. */
191 static MemFileChunk *chunk = nullptr;
192 size_t chunkoffset, readsize, totread;
193
194 undo->memchunk_identical = true;
195
196 if (size == 0) {
197 return 0;
198 }
199
200 if (seek != size_t(undo->reader.offset)) {
201 chunk = static_cast<MemFileChunk *>(undo->memfile->chunks.first);
202 seek = 0;
203
204 while (chunk) {
205 if (seek + chunk->size > size_t(undo->reader.offset)) {
206 break;
207 }
208 seek += chunk->size;
209 chunk = static_cast<MemFileChunk *>(chunk->next);
210 }
211 offset = seek;
212 seek = size_t(undo->reader.offset);
213 }
214
215 if (chunk) {
216 totread = 0;
217
218 do {
219 /* First check if it's on the end if current chunk. */
220 if (seek - offset == chunk->size) {
221 offset += chunk->size;
222 chunk = static_cast<MemFileChunk *>(chunk->next);
223 }
224
225 /* Debug, should never happen. */
226 if (chunk == nullptr) {
227 printf("illegal read, chunk zero\n");
228 return 0;
229 }
230
231 chunkoffset = seek - offset;
232 readsize = size - totread;
233
234 /* Data can be spread over multiple chunks, so clamp size
235 * to within this chunk, and then it will read further in
236 * the next chunk. */
237 if (chunkoffset + readsize > chunk->size) {
238 readsize = chunk->size - chunkoffset;
239 }
240
241 memcpy(POINTER_OFFSET(buffer, totread), chunk->buf + chunkoffset, readsize);
242 totread += readsize;
243 undo->reader.offset += (off64_t)readsize;
244 seek += readsize;
245
246 /* `is_identical` of current chunk represents whether it changed compared to previous undo
247 * step. this is fine in redo case, but not in undo case, where we need an extra flag
248 * defined when saving the next (future) step after the one we want to restore, as we are
249 * supposed to 'come from' that future undo step, and not the one before current one. */
250 undo->memchunk_identical &= undo->undo_direction == STEP_REDO ? chunk->is_identical :
251 chunk->is_identical_future;
252 } while (totread < size);
253
254 return int64_t(totread);
255 }
256
257 return 0;
258}
259
260static void undo_close(FileReader *reader)
261{
262 MEM_freeN(reader);
263}
264
265FileReader *BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
266{
267 UndoReader *undo = MEM_callocN<UndoReader>(__func__);
268
269 undo->memfile = memfile;
270 undo->undo_direction = undo_direction;
271
272 undo->reader.read = undo_read;
273 undo->reader.seek = nullptr;
274 undo->reader.close = undo_close;
275
276 return (FileReader *)undo;
277}
#define MAIN_ID_SESSION_UID_UNSET
const char * BKE_main_blendfile_path(const Main *bmain) ATTR_NONNULL()
Definition main.cc:887
@ STEP_REDO
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void * BLI_pophead(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:252
unsigned int uint
#define ELEM(...)
#define POINTER_OFFSET(v, ofs)
external readfile function prototypes.
BlendFileData * BLO_read_from_memfile(Main *oldmain, const char *filepath, MemFile *memfile, const BlendFileReadParams *params, ReportList *reports)
These structs are the foundation for all linked lists in the library system.
Read Guarded memory(de)allocation.
BMesh const char void * data
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
void clear()
Definition BLI_map.hh:1038
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
#define SIZE_MAX
#define printf(...)
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
FileReaderSeekFn seek
off64_t offset
FileReaderCloseFn close
FileReaderReadFn read
void * first
bool is_identical_future
const char * buf
blender::Map< uint64_t, blender::ImplicitSharingInfoAndData > sharing_info_by_address_id
MemFile * reference_memfile
blender::Map< uint, MemFileChunk * > id_session_uid_mapping
MemFile * written_memfile
MemFileChunk * reference_current_chunk
MemFileSharedStorage * shared_storage
ListBase chunks
size_t size
bool memchunk_identical
MemFile * memfile
FileReader reader
static void undo_close(FileReader *reader)
Definition undofile.cc:260
FileReader * BLO_memfile_new_filereader(MemFile *memfile, int undo_direction)
Definition undofile.cc:265
void BLO_memfile_chunk_add(MemFileWriteData *mem_data, const char *buf, size_t size)
Definition undofile.cc:128
void BLO_memfile_write_init(MemFileWriteData *mem_data, MemFile *written_memfile, MemFile *reference_memfile)
Definition undofile.cc:97
static int64_t undo_read(FileReader *reader, void *buffer, size_t size)
Definition undofile.cc:185
Main * BLO_memfile_main_get(MemFile *memfile, Main *bmain, Scene **r_scene)
Definition undofile.cc:166
void BLO_memfile_clear_future(MemFile *memfile)
Definition undofile.cc:90
void BLO_memfile_free(MemFile *memfile)
Definition undofile.cc:38
void BLO_memfile_merge(MemFile *first, MemFile *second)
Definition undofile.cc:59
void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
Definition undofile.cc:123