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