Blender V5.0
writefile.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
61
62#include <cerrno>
63#include <climits>
64#include <cstdio>
65#include <cstdlib>
66#include <cstring>
67#include <fcntl.h>
68#include <iomanip>
69#include <sstream>
70#include <xxhash.h>
71
72#ifdef WIN32
73# include "BLI_winstuff.h"
74# include "winsock2.h"
75# include <io.h>
76#else
77# include <unistd.h> /* FreeBSD, for write() and close(). */
78#endif
79
80#include <fmt/format.h>
81
82#include "BLI_utildefines.h"
83
84#include "CLG_log.h"
85
86/* Allow writefile to use deprecated functionality (for forward compatibility code). */
87#define DNA_DEPRECATED_ALLOW
88
90#include "DNA_genfile.h"
91#include "DNA_key_types.h"
92#include "DNA_print.hh"
93#include "DNA_sdna_pointers.hh"
94#include "DNA_sdna_types.h"
95#include "DNA_userdef_types.h"
97
98#include "BLI_endian_defines.h"
99#include "BLI_fileops.hh"
101#include "BLI_math_base.h"
102#include "BLI_math_matrix.h"
103#include "BLI_multi_value_map.hh"
104#include "BLI_path_utils.hh"
105#include "BLI_set.hh"
106#include "BLI_string.h"
107#include "BLI_threads.h"
108
109#include "MEM_guardedalloc.h" /* MEM_freeN */
110
111#include "BKE_asset.hh"
112#include "BKE_blender_version.h"
113#include "BKE_bpath.hh"
114#include "BKE_global.hh" /* For #Global `G`. */
115#include "BKE_idprop.hh"
116#include "BKE_idtype.hh"
117#include "BKE_layer.hh"
118#include "BKE_lib_id.hh"
119#include "BKE_lib_override.hh"
120#include "BKE_lib_query.hh"
121#include "BKE_library.hh"
122#include "BKE_main.hh"
123#include "BKE_main_namemap.hh"
124#include "BKE_node.hh"
125#include "BKE_packedFile.hh"
126#include "BKE_preferences.h"
127#include "BKE_report.hh"
128#include "BKE_workspace.hh"
129
130#include "DRW_engine.hh"
131
132#include "BLO_blend_validate.hh"
133#include "BLO_read_write.hh"
134#include "BLO_readfile.hh"
135#include "BLO_undofile.hh"
136#include "BLO_writefile.hh"
137
138#include "readfile.hh"
139
140#include <zstd.h>
141
142/* Make preferences read-only. */
143#define U (*((const UserDef *)&U))
144
149#define GENERATE_DEBUG_BLEND_FILE 0
150#define DEBUG_BLEND_FILE_SUFFIX ".debug.txt"
151
152/* ********* my write, buffered writing with minimum size chunks ************ */
153
154/* Use optimal allocation since blocks of this size are kept in memory for undo. */
155#define MEM_BUFFER_SIZE MEM_SIZE_OPTIMAL(1 << 17) /* 128kb */
156#define MEM_CHUNK_SIZE MEM_SIZE_OPTIMAL(1 << 15) /* ~32kb */
157
158#define ZSTD_BUFFER_SIZE (1 << 21) /* 2mb */
159#define ZSTD_CHUNK_SIZE (1 << 20) /* 1mb */
160
161#define ZSTD_COMPRESSION_LEVEL 3
162
163static CLG_LogRef LOG = {"blend.writefile"};
164
166// #define USE_WRITE_DATA_LEN
167
168/* -------------------------------------------------------------------- */
171
178
180 public:
181 virtual bool open(const char *filepath) = 0;
182 virtual bool close() = 0;
183 virtual bool write(const void *buf, size_t buf_len) = 0;
184
186 bool use_buf = true;
187};
188
189class RawWriteWrap : public WriteWrap {
190 public:
191 bool open(const char *filepath) override;
192 bool close() override;
193 bool write(const void *buf, size_t buf_len) override;
194
195 private:
196 int file_handle = 0;
197};
198
199bool RawWriteWrap::open(const char *filepath)
200{
201 int file;
202
203 file = BLI_open(filepath, O_BINARY + O_WRONLY + O_CREAT + O_TRUNC, 0666);
204
205 if (file != -1) {
206 file_handle = file;
207 return true;
208 }
209
210 return false;
211}
213{
214 return (::close(file_handle) != -1);
215}
216bool RawWriteWrap::write(const void *buf, size_t buf_len)
217{
218 return ::write(file_handle, buf, buf_len) == buf_len;
219}
220
221class ZstdWriteWrap : public WriteWrap {
222 WriteWrap &base_wrap;
223
224 ListBase threadpool = {};
225 ListBase tasks = {};
226 ThreadMutex mutex = {};
227 ThreadCondition condition = {};
228 int next_frame = 0;
229 int num_frames = 0;
230
231 ListBase frames = {};
232
233 bool write_error = false;
234
235 public:
236 ZstdWriteWrap(WriteWrap &base_wrap) : base_wrap(base_wrap) {}
237
238 bool open(const char *filepath) override;
239 bool close() override;
240 bool write(const void *buf, size_t buf_len) override;
241
242 private:
243 struct ZstdWriteBlockTask;
244 void write_task(ZstdWriteBlockTask *task);
245 void write_u32_le(uint32_t val);
246 void write_seekable_frames();
247};
248
251 void *data;
252 size_t size;
255
256 static void *write_task(void *userdata)
257 {
258 auto *task = static_cast<ZstdWriteBlockTask *>(userdata);
259 task->ww->write_task(task);
260 return nullptr;
261 }
262};
263
264void ZstdWriteWrap::write_task(ZstdWriteBlockTask *task)
265{
266 size_t out_buf_len = ZSTD_compressBound(task->size);
267 void *out_buf = MEM_mallocN(out_buf_len, "Zstd out buffer");
268 size_t out_size = ZSTD_compress(
269 out_buf, out_buf_len, task->data, task->size, ZSTD_COMPRESSION_LEVEL);
270
271 MEM_freeN(task->data);
272
274
275 while (next_frame != task->frame_number) {
276 BLI_condition_wait(&condition, &mutex);
277 }
278
279 if (ZSTD_isError(out_size)) {
280 write_error = true;
281 }
282 else {
283 if (base_wrap.write(out_buf, out_size)) {
284 ZstdFrame *frameinfo = MEM_mallocN<ZstdFrame>("zstd frameinfo");
285 frameinfo->uncompressed_size = task->size;
286 frameinfo->compressed_size = out_size;
287 BLI_addtail(&frames, frameinfo);
288 }
289 else {
290 write_error = true;
291 }
292 }
293
294 next_frame++;
295
296 BLI_mutex_unlock(&mutex);
297 BLI_condition_notify_all(&condition);
298
299 MEM_freeN(out_buf);
300}
301
302bool ZstdWriteWrap::open(const char *filepath)
303{
304 if (!base_wrap.open(filepath)) {
305 return false;
306 }
307
308 /* Leave one thread open for the main writing logic, unless we only have one HW thread. */
309 int num_threads = max_ii(1, BLI_system_thread_count() - 1);
310 BLI_threadpool_init(&threadpool, ZstdWriteBlockTask::write_task, num_threads);
311 BLI_mutex_init(&mutex);
312 BLI_condition_init(&condition);
313
314 return true;
315}
316
317void ZstdWriteWrap::write_u32_le(uint32_t val)
318{
319 /* NOTE: this is endianness-sensitive.
320 * This value must always be written as little-endian. */
322 base_wrap.write(&val, sizeof(uint32_t));
323}
324
325/* In order to implement efficient seeking when reading the .blend, we append
326 * a skippable frame that encodes information about the other frames present
327 * in the file.
328 * The format here follows the upstream spec for seekable files:
329 * https://github.com/facebook/zstd/blob/master/contrib/seekable_format/zstd_seekable_compression_format.md
330 * If this information is not present in a file (e.g. if it was compressed
331 * with external tools), it can still be opened in Blender, but seeking will
332 * not be supported, so more memory might be needed. */
333void ZstdWriteWrap::write_seekable_frames()
334{
335 /* Write seek table header (magic number and frame size). */
336 write_u32_le(0x184D2A5E);
337
338 /* The actual frame number might not match num_frames if there was a write error. */
339 const uint32_t num_frames = BLI_listbase_count(&frames);
340 /* Each frame consists of two u32, so 8 bytes each.
341 * After the frames, a footer containing two u32 and one byte (9 bytes total) is written. */
342 const uint32_t frame_size = num_frames * 8 + 9;
343 write_u32_le(frame_size);
344
345 /* Write seek table entries. */
346 LISTBASE_FOREACH (ZstdFrame *, frame, &frames) {
347 write_u32_le(frame->compressed_size);
348 write_u32_le(frame->uncompressed_size);
349 }
350
351 /* Write seek table footer (number of frames, option flags and second magic number). */
352 write_u32_le(num_frames);
353 const char flags = 0; /* We don't store checksums for each frame. */
354 base_wrap.write(&flags, 1);
355 write_u32_le(0x8F92EAB1);
356}
357
359{
360 BLI_threadpool_end(&threadpool);
361 BLI_freelistN(&tasks);
362
363 BLI_mutex_end(&mutex);
364 BLI_condition_end(&condition);
365
366 write_seekable_frames();
367 BLI_freelistN(&frames);
368
369 return base_wrap.close() && !write_error;
370}
371
372bool ZstdWriteWrap::write(const void *buf, const size_t buf_len)
373{
374 if (write_error) {
375 return false;
376 }
377
379 task->data = MEM_mallocN(buf_len, __func__);
380 memcpy(task->data, buf, buf_len);
381 task->size = buf_len;
382 task->frame_number = num_frames++;
383 task->ww = this;
384
385 BLI_mutex_lock(&mutex);
386 BLI_addtail(&tasks, task);
387
388 /* If there's a free worker thread, just push the block into that thread.
389 * Otherwise, we wait for the earliest thread to finish.
390 * We look up the earliest thread while holding the mutex, but release it
391 * before joining the thread to prevent a deadlock. */
392 ZstdWriteBlockTask *first_task = static_cast<ZstdWriteBlockTask *>(tasks.first);
393 BLI_mutex_unlock(&mutex);
394 if (!BLI_available_threads(&threadpool)) {
395 BLI_threadpool_remove(&threadpool, first_task);
396
397 /* If the task list was empty before we pushed our task, there should
398 * always be a free thread. */
399 BLI_assert(first_task != task);
400 BLI_remlink(&tasks, first_task);
401 MEM_freeN(first_task);
402 }
403 BLI_threadpool_insert(&threadpool, task);
404
405 return true;
406}
407
409
410/* -------------------------------------------------------------------- */
413
414struct WriteData {
415 const SDNA *sdna;
416 std::ostream *debug_dst = nullptr;
417
418 struct {
422 size_t used_len;
423
425 size_t max_size;
429
430#ifdef USE_WRITE_DATA_LEN
432 size_t write_len;
433#endif
434
437
439 struct {
451
452 struct {
457 std::unique_ptr<blender::dna::pointers::PointersInDNA> sdna_pointers;
491
497
502
509};
510
513};
514
516{
517 WriteData *wd = MEM_new<WriteData>(__func__);
518
520 wd->stable_address_ids.sdna_pointers = std::make_unique<blender::dna::pointers::PointersInDNA>(
521 *wd->sdna);
522
523 wd->ww = ww;
524
525 if ((ww == nullptr) || (ww->use_buf)) {
526 if (ww == nullptr) {
529 }
530 else {
533 }
534 wd->buffer.buf = MEM_malloc_arrayN<uchar>(wd->buffer.max_size, "wd->buffer.buf");
535 }
536
537 return wd;
538}
539
540static void writedata_do_write(WriteData *wd, const void *mem, const size_t memlen)
541{
542 if ((wd == nullptr) || wd->validation_data.critical_error || (mem == nullptr) || memlen < 1) {
543 return;
544 }
545
546 if (memlen > INT_MAX) {
547 BLI_assert_msg(0, "Cannot write chunks bigger than INT_MAX.");
548 return;
549 }
550
552 return;
553 }
554
555 /* Memory based save. */
556 if (wd->use_memfile) {
557 BLO_memfile_chunk_add(&wd->mem, static_cast<const char *>(mem), memlen);
558 }
559 else {
560 if (!wd->ww->write(mem, memlen)) {
562 }
563 }
564}
565
566static void writedata_free(WriteData *wd)
567{
568 if (wd->buffer.buf) {
569 MEM_freeN(wd->buffer.buf);
570 }
571 MEM_delete(wd);
572}
573
575
576/* -------------------------------------------------------------------- */
579
584static void mywrite_flush(WriteData *wd)
585{
586 if (wd->buffer.used_len != 0) {
588 wd->buffer.used_len = 0;
589 }
590}
591
597static void mywrite(WriteData *wd, const void *adr, size_t len)
598{
600 return;
601 }
602
603 if (UNLIKELY(adr == nullptr)) {
604 BLI_assert(0);
605 return;
606 }
607
608#ifdef USE_WRITE_DATA_LEN
609 wd->write_len += len;
610#endif
611
612 if (wd->buffer.buf == nullptr) {
613 writedata_do_write(wd, adr, len);
614 }
615 else {
616 /* If we have a single big chunk, write existing data in
617 * buffer and write out big chunk in smaller pieces. */
618 if (len > wd->buffer.chunk_size) {
619 if (wd->buffer.used_len != 0) {
621 wd->buffer.used_len = 0;
622 }
623
624 do {
625 const size_t writelen = std::min(len, wd->buffer.chunk_size);
626 writedata_do_write(wd, adr, writelen);
627 adr = (const char *)adr + writelen;
628 len -= writelen;
629 } while (len > 0);
630
631 return;
632 }
633
634 /* If data would overflow buffer, write out the buffer. */
635 if (len + wd->buffer.used_len > wd->buffer.max_size - 1) {
637 wd->buffer.used_len = 0;
638 }
639
640 /* Append data at end of buffer. */
641 memcpy(&wd->buffer.buf[wd->buffer.used_len], adr, len);
642 wd->buffer.used_len += len;
643 }
644}
645
653static WriteData *mywrite_begin(WriteWrap *ww, MemFile *compare, MemFile *current)
654{
655 WriteData *wd = writedata_new(ww);
656
657 if (current != nullptr) {
658 BLO_memfile_write_init(&wd->mem, current, compare);
659 wd->use_memfile = true;
660 }
661
662 return wd;
663}
664
671static bool mywrite_end(WriteData *wd)
672{
673 if (wd->buffer.used_len != 0) {
675 wd->buffer.used_len = 0;
676 }
677
678 if (wd->use_memfile) {
680 }
681
682 const bool err = wd->validation_data.critical_error;
683 writedata_free(wd);
684
685 return err;
686}
687
688static uint64_t get_stable_pointer_hint_for_id(const ID &id, const bool is_undo)
689{
690 /* Make the stable pointer depend on a specific data of the ID.
691 * Note that this is different when writing blendfile on disk, and when writing an undo step in
692 * memory (memfile).
693 *
694 * For the blendfile on disk, the ID name is used, together with its library if linked, as this
695 * is effectively the 'unique identifer' of IDs in blendfiles and accross linking, so if these
696 * change, it's also fine to get a different 'stable pointer'.
697 *
698 * For the undo memfile however, things are different: It is possible that a same ID name is
699 * reused for two different IDs in two different consecutive undo steps (see #149899). Getting
700 * the same stable pointer in this case can lead to falsely detecting other IDs using these as
701 * unchanged, leading to undo data corruption and crashes.
702 * In this case, using the session UID is a better source of info, as these are assumed unique
703 * during an editing session, and are extremely stable for a same ID (even if it is e.g.
704 * renamed).
705 */
706 if (is_undo) {
707 /* Note: Using the uint32_t session_uid also means that library data can be ignored (and ID
708 * made local always get a new session UID), and that there is no need to call the hashing code
709 * at all.
710 *
711 * However, to leave enough 'address space' for all the sub-data pointers, its value is shifted
712 * into higher significant bits of the returned value (only shift by 20 bits here, since
713 * #stable_id_from_hint also shifts further the generated values by 4, and some of the most
714 * significant bits are also reserved for flags, like the #implicit_sharing_address_id_flag
715 * one). */
716 return uint64_t(id.session_uid) << 20;
717 }
718
719 const uint64_t id_hash = XXH3_64bits(id.name, strlen(id.name));
720 if (!id.lib) {
721 return id_hash;
722 }
723
724 const uint64_t lib_hash = XXH3_64bits(id.lib->id.name, strlen(id.lib->id.name));
725 return id_hash ^ lib_hash;
726}
727
733static void mywrite_id_begin(WriteData *wd, ID *id)
734{
735 BLI_assert(wd->is_writing_id == false);
736 wd->is_writing_id = true;
737
739
740 BLI_assert_msg(ID_IS_PACKED(id) || id->deep_hash.is_null(),
741 "The only IDs with non-null deep-hash data should be packed linked ones");
742 BLI_assert_msg((id->flag & ID_FLAG_EMBEDDED_DATA) == 0 || id->deep_hash.is_null(),
743 "Embedded IDs should always have a null deep-hash data");
744
745 const bool is_undo = wd->use_memfile;
746
748
749 if (is_undo) {
750 wd->mem.current_id_session_uid = id->session_uid;
751
752 /* If current next memchunk does not match the ID we are about to write, or is not the _first_
753 * one for said ID, try to find the correct memchunk in the mapping using ID's session_uid. */
754 const MemFileChunk *curr_memchunk = wd->mem.reference_current_chunk;
755 const MemFileChunk *prev_memchunk = curr_memchunk != nullptr ?
756 static_cast<MemFileChunk *>(curr_memchunk->prev) :
757 nullptr;
758 if (curr_memchunk == nullptr || curr_memchunk->id_session_uid != id->session_uid ||
759 (prev_memchunk != nullptr &&
760 (prev_memchunk->id_session_uid == curr_memchunk->id_session_uid)))
761 {
763 nullptr))
764 {
766 }
767 /* Else, no existing memchunk found, i.e. this is supposed to be a new ID. */
768 }
769 /* Otherwise, we try with the current memchunk in any case, whether it is matching current
770 * ID's session_uid or not. */
771 }
772}
773
779static void mywrite_id_end(WriteData *wd, ID * /*id*/)
780{
781 if (wd->use_memfile) {
782 /* Very important to do it after every ID write now, otherwise we cannot know whether a
783 * specific ID changed or not. */
784 mywrite_flush(wd);
786 }
787
790
791 BLI_assert(wd->is_writing_id == true);
792 wd->is_writing_id = false;
793}
794
796
797/* -------------------------------------------------------------------- */
800
808static bool write_at_address_validate(WriteData *wd, const int filecode, const void *address)
809{
810 /* Skip in undo case. */
811 if (wd->use_memfile) {
812 return true;
813 }
814
815 if (wd->is_writing_id && filecode == BLO_CODE_DATA) {
816 if (!wd->validation_data.per_id_addresses_set.add(address)) {
818 "Same identifier (old address) used several times for a same ID, skipping this "
819 "block to avoid critical corruption of the Blender file.");
820 return false;
821 }
822 }
823 return true;
824}
825
826static void write_bhead(WriteData *wd, const BHead &bhead)
827{
828 if constexpr (sizeof(void *) == 4) {
829 /* Always write #BHead4 in 32 bit builds. */
830 BHead4 bh;
831 bh.code = bhead.code;
832 bh.old = uint32_t(uintptr_t(bhead.old));
833 bh.nr = bhead.nr;
834 bh.SDNAnr = bhead.SDNAnr;
835 bh.len = bhead.len;
836 mywrite(wd, &bh, sizeof(bh));
837 return;
838 }
839 /* Write new #LargeBHead8 headers if enabled. Older Blender versions can't read those. */
840 if (!USER_DEVELOPER_TOOL_TEST(&U, write_legacy_blend_file_format)) {
842 static_assert(sizeof(BHead) == sizeof(LargeBHead8));
843 mywrite(wd, &bhead, sizeof(bhead));
844 return;
845 }
846 }
847 /* Write older #SmallBHead8 headers so that Blender versions that don't support #LargeBHead8 can
848 * read the file. */
849 SmallBHead8 bh;
850 bh.code = bhead.code;
851 bh.old = uint64_t(bhead.old);
852 bh.nr = bhead.nr;
853 bh.SDNAnr = bhead.SDNAnr;
854 bh.len = bhead.len;
855 /* Check that the written buffer size is compatible with the limits of #SmallBHead8. */
856 if (bhead.len > std::numeric_limits<decltype(bh.len)>::max()) {
857 CLOG_ERROR(&LOG, "Written .blend file is corrupt, because a memory block is too large.");
858 return;
859 }
860 mywrite(wd, &bh, sizeof(bh));
861}
862
865
867{
868 /* Add a stride. This is not strictly necessary but may help with debugging later on because it's
869 * easier to identify bad ids. */
870 uint64_t stable_id = hint << 4;
871 if (stable_id == 0) {
872 /* Null values are reserved for nullptr. */
873 stable_id = (1 << 4);
874 }
875 /* Remove the first bit as it reserved for pointers for implicit sharing.*/
877 return stable_id;
878}
879
881{
882 uint64_t stable_id = stable_id_from_hint(hint);
883 while (!wd.stable_address_ids.used_ids.add(stable_id)) {
884 /* Generate a new hint because there is a collision. Collisions are generally expected to be
885 * very rare. It can happen when #get_stable_pointer_hint_for_id produces values that are very
886 * close for different IDs. */
887 hint = XXH3_64bits(&hint, sizeof(uint64_t));
888 stable_id = stable_id_from_hint(hint);
889 }
890 hint++;
891 return stable_id;
892}
893
904{
905 BLI_assert(data != nullptr);
906 uint64_t address_id = uint64_t(data);
907 /* Adding this bit so that it never overlap with an id generated by #stable_id_from_hint.
908 * Assuming that the given pointer is an actual pointer, it will stay unique when the
909 * #implicit_sharing_address_id_flag bit is set. That's because the upper bits of the pointer
910 * are effectively unused nowadays. */
912 return address_id;
913}
914
915static uint64_t get_address_id_int(WriteData &wd, const void *address)
916{
917 if (address == nullptr) {
918 return 0;
919 }
920 /* Either reuse an existing identifier or create a new one. */
921 return wd.stable_address_ids.pointer_map.lookup_or_add_cb(address, [&]() {
923 });
924}
925
926static const void *get_address_id(WriteData &wd, const void *address)
927{
928 return reinterpret_cast<const void *>(get_address_id_int(wd, address));
929}
930
932 const int filecode,
933 const int struct_nr,
934 const int64_t nr,
935 const void *adr,
936 const void *data)
937{
938 BLI_assert(struct_nr > 0 && struct_nr <= blender::dna::sdna_struct_id_get_max());
939
940 if (adr == nullptr || data == nullptr || nr == 0) {
941 return;
942 }
943
944 if (!write_at_address_validate(wd, filecode, adr)) {
945 return;
946 }
947
948 const int64_t len_in_bytes = nr * DNA_struct_size(wd->sdna, struct_nr);
950 USER_DEVELOPER_TOOL_TEST(&U, write_legacy_blend_file_format))
951 {
952 if (len_in_bytes > INT32_MAX) {
953 CLOG_ERROR(&LOG, "Cannot write chunks bigger than INT_MAX.");
954 return;
955 }
956 }
957
958 /* Get the address identifier that will be written to the file.*/
959 const void *address_id = get_address_id(*wd, adr);
960
961 const blender::dna::pointers::StructInfo &struct_info =
962 wd->stable_address_ids.sdna_pointers->get_for_struct(struct_nr);
963 const bool can_write_raw_runtime_data = struct_info.pointers.is_empty();
964
965 blender::DynamicStackBuffer<16 * 1024> buffer_owner(len_in_bytes, 64);
966 const void *data_to_write;
967 if (can_write_raw_runtime_data) {
968 /* The passed in data contains no pointers, so it can be written without an additional copy. */
969 data_to_write = data;
970 }
971 else {
972 void *buffer = buffer_owner.buffer();
973 data_to_write = buffer;
974 memcpy(buffer, data, len_in_bytes);
975
976 /* Overwrite pointers with their corresponding address identifiers. */
977 for (const int i : blender::IndexRange(nr)) {
978 for (const blender::dna::pointers::PointerInfo &pointer_info : struct_info.pointers) {
979 const int offset = i * struct_info.size_in_bytes + pointer_info.offset;
980 const void **p_ptr = reinterpret_cast<const void **>(POINTER_OFFSET(buffer, offset));
981 const void *address_id = get_address_id(*wd, *p_ptr);
982 *p_ptr = address_id;
983 }
984 }
985 }
986
987 BHead bh;
988 bh.code = filecode;
989 bh.old = address_id;
990 bh.nr = nr;
991 bh.SDNAnr = struct_nr;
992 bh.len = len_in_bytes;
993
994 if (bh.len == 0) {
995 return;
996 }
997
998 if (wd->debug_dst) {
1000 *wd->sdna, struct_nr, data_to_write, address_id, nr, *wd->debug_dst);
1001 }
1002
1003 write_bhead(wd, bh);
1004 mywrite(wd, data_to_write, size_t(bh.len));
1005}
1006
1007static void writestruct_nr(
1008 WriteData *wd, const int filecode, const int struct_nr, const int64_t nr, const void *adr)
1009{
1010 writestruct_at_address_nr(wd, filecode, struct_nr, nr, adr, adr);
1011}
1012
1014 const size_t len,
1015 const void *address_id,
1016 const void *data)
1017{
1018 fmt::memory_buffer buf;
1019 fmt::appender dst{buf};
1020
1021 fmt::format_to(dst, "<Raw Data> at {} ({} bytes)\n", address_id, len);
1022
1023 constexpr int bytes_per_row = 8;
1024 const int len_digits = std::to_string(std::max<size_t>(0, len - 1)).size();
1025
1026 for (size_t i = 0; i < len; i++) {
1027 if (i % bytes_per_row == 0) {
1028 fmt::format_to(dst, " {:{}}: ", i, len_digits);
1029 }
1030 fmt::format_to(dst, "{:02x} ", reinterpret_cast<const uint8_t *>(data)[i]);
1031 if (i % bytes_per_row == bytes_per_row - 1) {
1032 fmt::format_to(dst, "\n");
1033 }
1034 }
1035 if (len % bytes_per_row != 0) {
1036 fmt::format_to(dst, "\n");
1037 }
1038
1039 *wd->debug_dst << fmt::to_string(buf);
1040}
1041
1045static void writedata(
1046 WriteData *wd, const int filecode, const void *data, const size_t len, const void *adr)
1047{
1048 if (data == nullptr || len == 0) {
1049 return;
1050 }
1051
1052 if (!write_at_address_validate(wd, filecode, adr)) {
1053 return;
1054 }
1055
1057 USER_DEVELOPER_TOOL_TEST(&U, write_legacy_blend_file_format)) &&
1058 len > INT_MAX)
1059 {
1060 BLI_assert_msg(0, "Cannot write chunks bigger than INT_MAX.");
1061 return;
1062 }
1063
1064 const void *address_id = get_address_id(*wd, adr);
1065
1066 BHead bh;
1067 bh.code = filecode;
1068 bh.old = address_id;
1069 bh.nr = 1;
1070 BLI_STATIC_ASSERT(SDNA_RAW_DATA_STRUCT_INDEX == 0, "'raw data' SDNA struct index should be 0")
1072 bh.len = int64_t(len);
1073
1074 if (wd->debug_dst) {
1075 write_raw_data_in_debug_file(wd, len, address_id, adr);
1076 }
1077
1078 write_bhead(wd, bh);
1079 mywrite(wd, data, len);
1080}
1081
1082static void writedata(WriteData *wd, const int filecode, const size_t len, const void *adr)
1083{
1084 writedata(wd, filecode, adr, len, adr);
1085}
1086
1090static void writelist_nr(WriteData *wd,
1091 const int filecode,
1092 const int struct_nr,
1093 const ListBase *lb)
1094{
1095 const Link *link = static_cast<Link *>(lb->first);
1096
1097 while (link) {
1098 writestruct_nr(wd, filecode, struct_nr, 1, link);
1099 link = link->next;
1100 }
1101}
1102
1103#if 0
1104static void writelist_id(WriteData *wd, const int filecode, const char *structname, const ListBase *lb)
1105{
1106 const Link *link = lb->first;
1107 if (link) {
1108
1109 const int struct_nr = DNA_struct_find_with_alias(wd->sdna, structname);
1110 if (struct_nr == -1) {
1111 printf("error: cannot find SDNA code <%s>\n", structname);
1112 return;
1113 }
1114
1115 while (link) {
1116 writestruct_nr(wd, filecode, struct_nr, 1, link);
1117 link = link->next;
1118 }
1119 }
1120}
1121#endif
1122
1123#define writestruct_at_address(wd, filecode, struct_id, nr, adr, data) \
1124 writestruct_at_address_nr( \
1125 wd, filecode, blender::dna::sdna_struct_id_get<struct_id>(), nr, adr, data)
1126
1127#define writestruct(wd, filecode, struct_id, nr, adr) \
1128 writestruct_nr(wd, filecode, blender::dna::sdna_struct_id_get<struct_id>(), nr, adr)
1129
1131
1132/* -------------------------------------------------------------------- */
1137
1142static void current_screen_compat(Main *mainvar,
1143 const bool use_active_win,
1144 bScreen **r_screen,
1145 Scene **r_scene,
1146 ViewLayer **r_view_layer)
1147{
1148 wmWindowManager *wm;
1149 wmWindow *window = nullptr;
1150
1151 /* Find a global current screen in the first open window, to have
1152 * a reasonable default for reading in older versions. */
1153 wm = static_cast<wmWindowManager *>(mainvar->wm.first);
1154
1155 if (wm) {
1156 if (use_active_win) {
1157 /* Write the active window into the file, needed for multi-window undo #43424. */
1158 for (window = static_cast<wmWindow *>(wm->windows.first); window; window = window->next) {
1159 if (window->active) {
1160 break;
1161 }
1162 }
1163
1164 /* Fallback. */
1165 if (window == nullptr) {
1166 window = static_cast<wmWindow *>(wm->windows.first);
1167 }
1168 }
1169 else {
1170 window = static_cast<wmWindow *>(wm->windows.first);
1171 }
1172 }
1173
1174 *r_screen = (window) ? BKE_workspace_active_screen_get(window->workspace_hook) : nullptr;
1175 *r_scene = (window) ? window->scene : nullptr;
1176 *r_view_layer = (window && *r_scene) ? BKE_view_layer_find(*r_scene, window->view_layer_name) :
1177 nullptr;
1178}
1179
1181 int sfra;
1182 int efra;
1184};
1185
1192static void write_renderinfo(WriteData *wd, Main *mainvar)
1193{
1194 bScreen *curscreen;
1195 Scene *curscene = nullptr;
1196 ViewLayer *view_layer;
1197
1198 /* XXX: in future, handle multiple windows with multiple screens? */
1199 current_screen_compat(mainvar, false, &curscreen, &curscene, &view_layer);
1200
1201 LISTBASE_FOREACH (Scene *, sce, &mainvar->scenes) {
1202 if (!ID_IS_LINKED(sce) && (sce == curscene || (sce->r.scemode & R_BG_RENDER))) {
1204 data.sfra = sce->r.sfra;
1205 data.efra = sce->r.efra;
1206 memset(data.scene_name, 0, sizeof(data.scene_name));
1207
1208 STRNCPY(data.scene_name, sce->id.name + 2);
1209
1210 writedata(wd, BLO_CODE_REND, sizeof(data), &data);
1211 }
1212 }
1213}
1214
1215static void write_keymapitem(BlendWriter *writer, const wmKeyMapItem *kmi)
1216{
1217 BLO_write_struct(writer, wmKeyMapItem, kmi);
1218 if (kmi->properties) {
1219 IDP_BlendWrite(writer, kmi->properties);
1220 }
1221}
1222
1223static void write_userdef(BlendWriter *writer, const UserDef *userdef)
1224{
1225 writestruct(writer->wd, BLO_CODE_USER, UserDef, 1, userdef);
1226
1227 LISTBASE_FOREACH (const bTheme *, btheme, &userdef->themes) {
1228 BLO_write_struct(writer, bTheme, btheme);
1229 }
1230
1231 LISTBASE_FOREACH (const wmKeyMap *, keymap, &userdef->user_keymaps) {
1232 BLO_write_struct(writer, wmKeyMap, keymap);
1233
1234 LISTBASE_FOREACH (const wmKeyMapDiffItem *, kmdi, &keymap->diff_items) {
1235 BLO_write_struct(writer, wmKeyMapDiffItem, kmdi);
1236 if (kmdi->remove_item) {
1237 write_keymapitem(writer, kmdi->remove_item);
1238 }
1239 if (kmdi->add_item) {
1240 write_keymapitem(writer, kmdi->add_item);
1241 }
1242 }
1243
1244 LISTBASE_FOREACH (const wmKeyMapItem *, kmi, &keymap->items) {
1245 write_keymapitem(writer, kmi);
1246 }
1247 }
1248
1249 LISTBASE_FOREACH (const wmKeyConfigPref *, kpt, &userdef->user_keyconfig_prefs) {
1250 BLO_write_struct(writer, wmKeyConfigPref, kpt);
1251 if (kpt->prop) {
1252 IDP_BlendWrite(writer, kpt->prop);
1253 }
1254 }
1255
1256 LISTBASE_FOREACH (const bUserMenu *, um, &userdef->user_menus) {
1257 BLO_write_struct(writer, bUserMenu, um);
1258 LISTBASE_FOREACH (const bUserMenuItem *, umi, &um->items) {
1259 if (umi->type == USER_MENU_TYPE_OPERATOR) {
1260 const bUserMenuItem_Op *umi_op = (const bUserMenuItem_Op *)umi;
1261 BLO_write_struct(writer, bUserMenuItem_Op, umi_op);
1262 if (umi_op->prop) {
1263 IDP_BlendWrite(writer, umi_op->prop);
1264 }
1265 }
1266 else if (umi->type == USER_MENU_TYPE_MENU) {
1267 const bUserMenuItem_Menu *umi_mt = (const bUserMenuItem_Menu *)umi;
1268 BLO_write_struct(writer, bUserMenuItem_Menu, umi_mt);
1269 }
1270 else if (umi->type == USER_MENU_TYPE_PROP) {
1271 const bUserMenuItem_Prop *umi_pr = (const bUserMenuItem_Prop *)umi;
1272 BLO_write_struct(writer, bUserMenuItem_Prop, umi_pr);
1273 }
1274 else {
1275 BLO_write_struct(writer, bUserMenuItem, umi);
1276 }
1277 }
1278 }
1279
1280 LISTBASE_FOREACH (const bAddon *, bext, &userdef->addons) {
1281 BLO_write_struct(writer, bAddon, bext);
1282 if (bext->prop) {
1283 IDP_BlendWrite(writer, bext->prop);
1284 }
1285 }
1286
1287 LISTBASE_FOREACH (const bPathCompare *, path_cmp, &userdef->autoexec_paths) {
1288 BLO_write_struct(writer, bPathCompare, path_cmp);
1289 }
1290
1291 LISTBASE_FOREACH (const bUserScriptDirectory *, script_dir, &userdef->script_directories) {
1292 BLO_write_struct(writer, bUserScriptDirectory, script_dir);
1293 }
1294
1295 LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library_ref, &userdef->asset_libraries) {
1296 BLO_write_struct(writer, bUserAssetLibrary, asset_library_ref);
1297 }
1298
1299 LISTBASE_FOREACH (const bUserExtensionRepo *, repo_ref, &userdef->extension_repos) {
1300 BLO_write_struct(writer, bUserExtensionRepo, repo_ref);
1302 }
1304 const bUserAssetShelfSettings *, shelf_settings, &userdef->asset_shelves_settings)
1305 {
1306 BLO_write_struct(writer, bUserAssetShelfSettings, shelf_settings);
1307 BKE_asset_catalog_path_list_blend_write(writer, shelf_settings->enabled_catalog_paths);
1308 }
1309
1310 LISTBASE_FOREACH (const uiStyle *, style, &userdef->uistyles) {
1311 BLO_write_struct(writer, uiStyle, style);
1312 }
1313}
1314
1318static void write_id(WriteData *wd, ID *id)
1319{
1320 const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
1321 mywrite_id_begin(wd, id);
1322 if (id_type->blend_write != nullptr) {
1323 BlendWriter writer = {wd};
1324 BLO_Write_IDBuffer id_buffer{*id, wd->use_memfile, false};
1325 id_type->blend_write(&writer, id_buffer.get(), id);
1326 }
1327 mywrite_id_end(wd, id);
1328}
1329
1330static void write_id_placeholder(WriteData *wd, ID *id)
1331{
1332 mywrite_id_begin(wd, id);
1333
1334 /* Only copy required data for the placeholder ID. */
1335 BLO_Write_IDBuffer id_buffer{*id, wd->use_memfile, true};
1336
1337 writestruct_at_address(wd, ID_LINK_PLACEHOLDER, ID, 1, id, &id_buffer);
1338
1339 mywrite_id_end(wd, id);
1340}
1341
1343static void write_libraries(WriteData *wd, Main *bmain)
1344{
1345 /* Gather IDs coming from each library. */
1346 blender::MultiValueMap<Library *, ID *> linked_ids_by_library;
1347 {
1348 ID *id;
1349 FOREACH_MAIN_ID_BEGIN (bmain, id) {
1350 if (!ID_IS_LINKED(id)) {
1351 continue;
1352 }
1353 BLI_assert(id->lib);
1354 linked_ids_by_library.add(id->lib, id);
1355 }
1357 }
1358
1359 LISTBASE_FOREACH (Library *, library_ptr, &bmain->libraries) {
1360 Library &library = *library_ptr;
1361 const blender::Span<ID *> ids = linked_ids_by_library.lookup(&library);
1362
1363 /* Gather IDs that are somehow directly referenced by data in the current blend file. */
1364 blender::Vector<ID *> ids_used_from_library;
1365 for (ID *id : ids) {
1366 if (id->us == 0) {
1367 continue;
1368 }
1369 if (ID_IS_PACKED(id)) {
1371 ids_used_from_library.append(id);
1372 continue;
1373 }
1374 if (id->tag & ID_TAG_EXTERN) {
1375 ids_used_from_library.append(id);
1376 continue;
1377 }
1378 if ((id->tag & ID_TAG_INDIRECT) && (id->flag & ID_FLAG_INDIRECT_WEAK_LINK)) {
1379 ids_used_from_library.append(id);
1380 continue;
1381 }
1382 }
1383
1384 bool should_write_library = false;
1385 if (library.packedfile) {
1386 should_write_library = true;
1387 }
1388 else if (!library.runtime->archived_libraries.is_empty()) {
1389 /* Reference 'real' blendfile library of archived 'copies' of it containing packed linked
1390 * IDs should always be written. */
1391 /* FIXME: A bit weak, as it could be that all archive libs are now empty (if all related
1392 * packed linked IDs have been deleted e.g.)...
1393 * Could be fixed by either adding more checks here, or ensuring empty archive libs are
1394 * deleted when no ID uses them anymore? */
1395 should_write_library = true;
1396 }
1397 else if (wd->use_memfile) {
1398 /* When writing undo step we always write all existing libraries. That makes reading undo
1399 * step much easier when dealing with purely indirectly used libraries. */
1400 should_write_library = true;
1401 }
1402 else {
1403 should_write_library = !ids_used_from_library.is_empty();
1404 }
1405
1406 if (!should_write_library) {
1407 /* Nothing from the library is used, so it does not have to be written. */
1408 continue;
1409 }
1410
1411 write_id(wd, &library.id);
1412
1413 /* Write placeholders for linked data-blocks that are used, and real IDs for the packed linked
1414 * ones. */
1415 for (ID *id : ids_used_from_library) {
1416 if (ID_IS_PACKED(id)) {
1417 write_id(wd, id);
1418 }
1419 else {
1420 if (!BKE_idtype_idcode_is_linkable(GS(id->name))) {
1421 CLOG_ERROR(&LOG,
1422 "Data-block '%s' from lib '%s' is not linkable, but is flagged as "
1423 "directly linked",
1424 id->name,
1425 library.runtime->filepath_abs);
1426 }
1427 write_id_placeholder(wd, id);
1428 }
1429 }
1430 }
1431
1432 mywrite_flush(wd);
1433}
1434
1435#ifdef WITH_BUILDINFO
1436extern "C" ulong build_commit_timestamp;
1437extern "C" char build_hash[];
1438#endif
1439
1445static void write_global(WriteData *wd, const int fileflags, Main *mainvar)
1446{
1447 const bool is_undo = wd->use_memfile;
1448 FileGlobal fg;
1449 bScreen *screen;
1450 Scene *scene;
1451 ViewLayer *view_layer;
1452 char subvstr[8];
1453
1454 /* Prevent memory checkers from complaining. */
1455 memset(fg._pad, 0, sizeof(fg._pad));
1456 memset(fg.filepath, 0, sizeof(fg.filepath));
1457 memset(fg.build_hash, 0, sizeof(fg.build_hash));
1458 fg._pad1 = nullptr;
1459
1460 current_screen_compat(mainvar, is_undo, &screen, &scene, &view_layer);
1461
1462 /* XXX: still remap `G`. */
1463 fg.curscreen = screen;
1464 fg.curscene = scene;
1465 fg.cur_view_layer = view_layer;
1466
1469
1470 /* Prevent to save this, is not good convention, and feature with concerns. */
1471 fg.fileflags = (fileflags & ~G_FILE_FLAG_ALL_RUNTIME);
1472
1473 fg.globalf = G.f;
1474 /* Write information needed for recovery. */
1475 if (fileflags & G_FILE_RECOVER_WRITE) {
1476 STRNCPY(fg.filepath, mainvar->filepath);
1477 /* Compression is often turned of when writing recovery files. However, when opening the file,
1478 * it should be enabled again. */
1479 fg.fileflags = G.fileflags & G_FILE_COMPRESS;
1480 }
1481 SNPRINTF(subvstr, "%4d", BLENDER_FILE_SUBVERSION);
1482 memcpy(fg.subvstr, subvstr, 4);
1483
1487#ifdef WITH_BUILDINFO
1488 /* TODO(sergey): Add branch name to file as well? */
1491#else
1493 STRNCPY(fg.build_hash, "unknown");
1494#endif
1495 writestruct(wd, BLO_CODE_GLOB, FileGlobal, 1, &fg);
1496}
1497
1503static void write_thumb(WriteData *wd, const BlendThumbnail *thumb)
1504{
1505 if (thumb) {
1506 writedata(wd, BLO_CODE_TEST, BLEN_THUMB_MEMSIZE_FILE(thumb->width, thumb->height), thumb);
1507 }
1508}
1509
1511
1512/* -------------------------------------------------------------------- */
1515
1516BLO_Write_IDBuffer::BLO_Write_IDBuffer(ID &id, const bool is_undo, const bool is_placeholder)
1517 : buffer_(is_placeholder ? sizeof(ID) : BKE_idtype_get_info_from_id(&id)->struct_size,
1518 alignof(ID))
1519{
1520 const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(&id);
1521 ID *temp_id = static_cast<ID *>(buffer_.buffer());
1522
1523 if (is_undo) {
1524 /* Record the changes that happened up to this undo push in
1525 * recalc_up_to_undo_push, and clear `recalc_after_undo_push` again
1526 * to start accumulating for the next undo push. */
1527 id.recalc_up_to_undo_push = id.recalc_after_undo_push;
1528 id.recalc_after_undo_push = 0;
1529 }
1530
1531 /* Copy ID data itself into buffer, to be able to freely modify it. */
1532
1533 if (is_placeholder) {
1534 /* For placeholders (references to linked data), zero-initialize, and only explicitly copy the
1535 * very small subset of required data. */
1536 *temp_id = ID{};
1537 temp_id->lib = id.lib;
1538 STRNCPY(temp_id->name, id.name);
1539 temp_id->flag = id.flag;
1540 temp_id->session_uid = id.session_uid;
1541 if (is_undo) {
1542 temp_id->recalc_up_to_undo_push = id.recalc_up_to_undo_push;
1543 temp_id->tag = id.tag & ID_TAG_KEEP_ON_UNDO;
1544 }
1545 return;
1546 }
1547
1548 /* Regular 'full' ID writing, copy everything, then clear some runtime data irrelevant in the
1549 * blendfile. */
1550 memcpy(temp_id, &id, id_type->struct_size);
1551
1552 /* Clear runtime data to reduce false detection of changed data in undo/redo context. */
1553 if (is_undo) {
1554 temp_id->tag &= ID_TAG_KEEP_ON_UNDO;
1555 }
1556 else {
1557 temp_id->tag = 0;
1558 }
1559 temp_id->us = 0;
1560 temp_id->icon_id = 0;
1561 temp_id->runtime = nullptr;
1562 /* Those listbase data change every time we add/remove an ID, and also often when
1563 * renaming one (due to re-sorting). This avoids generating a lot of false 'is changed'
1564 * detections between undo steps. */
1565 temp_id->prev = nullptr;
1566 temp_id->next = nullptr;
1567 /* Those runtime pointers should never be set during writing stage, but just in case clear
1568 * them too. */
1569 temp_id->orig_id = nullptr;
1570 temp_id->newid = nullptr;
1571 /* Even though in theory we could be able to preserve this python instance across undo even
1572 * when we need to re-read the ID into its original address, this is currently cleared in
1573 * #direct_link_id_common in `readfile.cc` anyway. */
1574 temp_id->py_instance = nullptr;
1575}
1576
1581
1582/* Helper callback for checking linked IDs used by given ID (assumed local), to ensure directly
1583 * linked data is tagged accordingly. */
1585{
1586 ID *self_id = cb_data->self_id;
1587 ID *id = *cb_data->id_pointer;
1588 const LibraryForeachIDCallbackFlag cb_flag = cb_data->cb_flag;
1589
1590 if (id == nullptr || !ID_IS_LINKED(id)) {
1591 return IDWALK_RET_NOP;
1592 }
1593 BLI_assert(!ID_IS_LINKED(self_id));
1594 BLI_assert((cb_flag & IDWALK_CB_INDIRECT_USAGE) == 0);
1595
1596 if (self_id->tag & ID_TAG_RUNTIME) {
1597 return IDWALK_RET_NOP;
1598 }
1599
1600 if (cb_flag & IDWALK_CB_WRITEFILE_IGNORE) {
1601 /* Do not consider these ID usages (typically, from the Outliner e.g.) as making the ID
1602 * directly linked. */
1603 return IDWALK_RET_NOP;
1604 }
1605
1607 /* Usages of unlinkable IDs (aka ShapeKeys and some UI IDs) should never cause them to be
1608 * considered as directly linked. This can often happen e.g. from UI data (the Outliner will
1609 * have links to most IDs).
1610 */
1611 return IDWALK_RET_NOP;
1612 }
1613
1614 if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK) {
1616 }
1617 else {
1618 id_lib_extern(id);
1619 }
1620
1621 return IDWALK_RET_NOP;
1622}
1623
1624static std::string get_blend_file_header()
1625{
1627 !USER_DEVELOPER_TOOL_TEST(&U, write_legacy_blend_file_format))
1628 {
1629 const int header_size_in_bytes = SIZEOFBLENDERHEADER_VERSION_1;
1630
1631 /* New blend file header format. */
1632 std::stringstream ss;
1633 ss << "BLENDER";
1634 ss << header_size_in_bytes;
1635 ss << '-';
1636 ss << std::setfill('0') << std::setw(2) << BLEND_FILE_FORMAT_VERSION_1;
1637 ss << 'v';
1638 ss << std::setfill('0') << std::setw(4) << BLENDER_FILE_VERSION;
1639
1640 const std::string header = ss.str();
1641 BLI_assert(header.size() == header_size_in_bytes);
1642 return header;
1643 }
1644
1645 const char pointer_size_char = sizeof(void *) == 8 ? '-' : '_';
1646 const char endian_char = 'v';
1647
1648 /* Legacy blend file header format. */
1649 std::stringstream ss;
1650 ss << "BLENDER";
1651 ss << pointer_size_char;
1652 ss << endian_char;
1654 const std::string header = ss.str();
1655 BLI_assert(header.size() == SIZEOFBLENDERHEADER_VERSION_0);
1656 return header;
1657}
1658
1660{
1661 const std::string header = get_blend_file_header();
1662 mywrite(wd, header.data(), header.size());
1663}
1664
1668static blender::Vector<ID *> gather_local_ids_to_write(Main *bmain, const bool is_undo)
1669{
1670 blender::Vector<ID *> local_ids_to_write;
1671 ID *id;
1672 FOREACH_MAIN_ID_BEGIN (bmain, id) {
1673 if (GS(id->name) == ID_LI) {
1674 /* Libraries are handled separately below. */
1675 continue;
1676 }
1677 if (ID_IS_LINKED(id)) {
1678 /* Linked data-blocks are handled separately below. */
1679 continue;
1680 }
1681 const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
1682 UNUSED_VARS_NDEBUG(id_type);
1683 /* We should never attempt to write non-regular IDs
1684 * (i.e. all kind of temp/runtime ones). */
1686 /* We only write unused IDs in undo case. */
1687 if (!is_undo) {
1688 /* NOTE: All 'never unused' local IDs (Scenes, WindowManagers, ...) should always be
1689 * written to disk, so their user-count should never be zero currently. Note that
1690 * libraries have already been skipped above, as they need a specific handling. */
1691 if (id->us == 0) {
1692 /* FIXME: #124857: Some old files seem to cause incorrect handling of their temp
1693 * screens.
1694 *
1695 * See e.g. file attached to #124777 (from 2.79.1).
1696 *
1697 * For now ignore, issue is not obvious to track down (`temp` bScreen ID from read data
1698 * _does_ have the proper `temp` tag), and seems anecdotal at worst. */
1699 BLI_assert((id_type->flags & IDTYPE_FLAGS_NEVER_UNUSED) == 0);
1700 continue;
1701 }
1702
1703 /* XXX Special handling for ShapeKeys, as having unused shape-keys is not a good thing
1704 * (and reported as error by e.g. `BLO_main_validate_shapekeys`), skip writing shape-keys
1705 * when their 'owner' is not written.
1706 *
1707 * NOTE: Since ShapeKeys are conceptually embedded IDs (like root node trees e.g.), this
1708 * behavior actually makes sense anyway. This remains more of a temp hack until topic of
1709 * how to handle unused data on save is properly tackled. */
1710 if (GS(id->name) == ID_KE) {
1711 Key *shape_key = reinterpret_cast<Key *>(id);
1712 /* NOTE: Here we are accessing the real owner ID data, not it's 'proxy' shallow copy
1713 * generated for its file-writing. This is not expected to be an issue, but is worth
1714 * noting. */
1715 if (shape_key->from == nullptr || shape_key->from->us == 0) {
1716 continue;
1717 }
1718 }
1719 }
1720
1721 if ((id->tag & ID_TAG_RUNTIME) != 0 && !is_undo) {
1722 /* Runtime IDs are never written to .blend files, and they should not influence
1723 * (in)direct status of linked IDs they may use. */
1724 continue;
1725 }
1726
1727 local_ids_to_write.append(id);
1728 }
1730 return local_ids_to_write;
1731}
1732
1738{
1739 ID *id;
1740 FOREACH_MAIN_ID_BEGIN (&bmain, id) {
1741 /* Ensure no other stable pointer has been created before. */
1743
1744 /* Derive the stable pointer from the id/library name which is independent of the write-order
1745 * of data-blocks. */
1747 const uint64_t address_id = get_next_stable_address_id(wd, hint);
1748
1749 /* Store the computed stable pointer so that it is used whenever the data-block is written or
1750 * referenced. */
1751 wd.stable_address_ids.pointer_map.add(id, address_id);
1752 }
1754}
1755
1762static bool write_file_handle(Main *mainvar,
1763 WriteWrap *ww,
1764 MemFile *compare,
1765 MemFile *current,
1766 const int write_flags,
1767 const bool use_userdef,
1768 const BlendThumbnail *thumb,
1769 std::ostream *debug_dst)
1770{
1771 WriteData *wd;
1772
1773 wd = mywrite_begin(ww, compare, current);
1774 wd->debug_dst = debug_dst;
1775 BlendWriter writer = {wd};
1776
1777 prepare_stable_data_block_ids(*wd, *mainvar);
1778
1779 /* Clear 'directly linked' flag for all linked data, these are not necessarily valid/up-to-date
1780 * info, they will be re-generated while write code is processing local IDs below. */
1781 if (!wd->use_memfile) {
1782 ID *id_iter;
1783 FOREACH_MAIN_ID_BEGIN (mainvar, id_iter) {
1784 if (ID_IS_LINKED(id_iter) && BKE_idtype_idcode_is_linkable(GS(id_iter->name))) {
1785 if (USER_DEVELOPER_TOOL_TEST(&U, use_all_linked_data_direct)) {
1786 /* Forces all linked data to be considered as directly linked.
1787 * FIXME: Workaround some BAT tool limitations for Heist production, should be removed
1788 * asap afterward. */
1789 id_lib_extern(id_iter);
1790 }
1791 else if (GS(id_iter->name) == ID_SCE) {
1792 /* For scenes, do not force them into 'indirectly linked' status.
1793 * The main reason is that scenes typically have no users, so most linked scene would be
1794 * systematically 'lost' on file save.
1795 *
1796 * While this change re-introduces the 'no-more-used data laying around in files for
1797 * ever' issue when it comes to scenes, this solution seems to be the most sensible one
1798 * for the time being, considering that:
1799 * - Scene are a top-level container.
1800 * - Linked scenes are typically explicitly linked by the user.
1801 * - Cases where scenes would be indirectly linked by other data (e.g. when linking a
1802 * collection or material) can be considered at the very least as not following sane
1803 * practice in data dependencies.
1804 * - There are typically not hundreds of scenes in a file, and they are always very
1805 * easily discoverable and browsable from the main UI. */
1806 }
1807 else {
1808 id_iter->tag |= ID_TAG_INDIRECT;
1809 id_iter->tag &= ~ID_TAG_EXTERN;
1810 }
1811 }
1812 }
1814 }
1815
1816 /* Recompute all ID user-counts if requested. Allows to avoid skipping writing of IDs wrongly
1817 * detected as unused due to invalid user-count. */
1818 if (!wd->use_memfile) {
1819 if (USER_DEVELOPER_TOOL_TEST(&U, use_recompute_usercount_on_save_debug)) {
1820 BKE_main_id_refcount_recompute(mainvar, false);
1821 }
1822 }
1823
1825 write_renderinfo(wd, mainvar);
1826 write_thumb(wd, thumb);
1827 write_global(wd, write_flags, mainvar);
1828
1829 /* The window-manager and screen often change,
1830 * avoid thumbnail detecting changes because of this. */
1831 mywrite_flush(wd);
1832
1833 const bool is_undo = wd->use_memfile;
1834 blender::Vector<ID *> local_ids_to_write = gather_local_ids_to_write(mainvar, is_undo);
1835
1836 if (!is_undo) {
1837 /* If not writing undo data, properly set directly linked IDs as `ID_TAG_EXTERN`. */
1838 for (ID *id : local_ids_to_write) {
1840 id,
1842 nullptr,
1844 }
1845
1846 /* Forcefully ensure we know about all needed override operations. */
1847 for (ID *id : local_ids_to_write) {
1849 BKE_lib_override_library_operations_create(mainvar, id, nullptr);
1850 }
1851 }
1852 }
1853
1854 /* Actually write local data-blocks to the file. */
1855 for (ID *id : local_ids_to_write) {
1856 write_id(wd, id);
1857 }
1858
1859 /* Write libraries about libraries and linked data-blocks. */
1860 write_libraries(wd, mainvar);
1861
1862 /* So changes above don't cause a 'DNA1' to be detected as changed on undo. */
1863 mywrite_flush(wd);
1864
1865 if (use_userdef) {
1866 write_userdef(&writer, &U);
1867 }
1868
1869 /* Write DNA last, because (to be implemented) test for which structs are written.
1870 *
1871 * Note that we *borrow* the pointer to 'DNAstr',
1872 * so writing each time uses the same address and doesn't cause unnecessary undo overhead. */
1873 writedata(wd, BLO_CODE_DNA1, size_t(wd->sdna->data_size), wd->sdna->data);
1874
1875 /* End of file. */
1876 BHead bhead{};
1877 bhead.code = BLO_CODE_ENDB;
1878 write_bhead(wd, bhead);
1879
1880 return mywrite_end(wd);
1881}
1882
1887static bool do_history(const char *filepath, ReportList *reports)
1888{
1889 /* Add 2 because version number maximum is double-digits. */
1890 char filepath_tmp1[FILE_MAX + 2], filepath_tmp2[FILE_MAX + 2];
1891 int version_number = min_ii(99, U.versions);
1892
1893 if (version_number == 0) {
1894 return true;
1895 }
1896
1897 if (strlen(filepath) < 2) {
1898 BKE_report(reports, RPT_ERROR, "Unable to make version backup: filename too short");
1899 return false;
1900 }
1901
1902 while (version_number > 1) {
1903 SNPRINTF(filepath_tmp1, "%s%d", filepath, version_number - 1);
1904 if (BLI_exists(filepath_tmp1)) {
1905 SNPRINTF(filepath_tmp2, "%s%d", filepath, version_number);
1906
1907 if (BLI_rename_overwrite(filepath_tmp1, filepath_tmp2)) {
1908 BKE_report(reports, RPT_ERROR, "Unable to make version backup");
1909 return false;
1910 }
1911 }
1912 version_number--;
1913 }
1914
1915 /* Needed when `version_number == 1`. */
1916 if (BLI_exists(filepath)) {
1917 SNPRINTF(filepath_tmp1, "%s%d", filepath, version_number);
1918
1919 if (BLI_rename_overwrite(filepath, filepath_tmp1)) {
1920 BKE_report(reports, RPT_ERROR, "Unable to make version backup");
1921 return false;
1922 }
1923 }
1924
1925 return true;
1926}
1927
1928static void write_file_main_validate_pre(Main *bmain, ReportList *reports)
1929{
1930 if (!bmain->lock) {
1931 return;
1932 }
1933
1934 if (G.debug & G_DEBUG_IO) {
1935 BKE_report(
1936 reports, RPT_DEBUG, "Checking validity of current .blend file *BEFORE* save to disk");
1937 }
1938
1939 BLO_main_validate_shapekeys(bmain, reports);
1940 if (!BKE_main_namemap_validate_and_fix(*bmain)) {
1941 BKE_report(reports,
1942 RPT_ERROR,
1943 "Critical data corruption: Conflicts and/or otherwise invalid data-blocks names "
1944 "(see console for details)");
1945 }
1946
1947 if (G.debug & G_DEBUG_IO) {
1948 BLO_main_validate_libraries(bmain, reports);
1949 }
1950}
1951
1952static void write_file_main_validate_post(Main *bmain, ReportList *reports)
1953{
1954 if (!bmain->lock) {
1955 return;
1956 }
1957
1958 if (G.debug & G_DEBUG_IO) {
1959 BKE_report(
1960 reports, RPT_DEBUG, "Checking validity of current .blend file *BEFORE* save to disk");
1961 BLO_main_validate_libraries(bmain, reports);
1962 }
1963}
1964
1965static bool BLO_write_file_impl(Main *mainvar,
1966 const char *filepath,
1967 const int write_flags,
1969 ReportList *reports,
1970 WriteWrap &ww)
1971{
1972 BLI_assert(!BLI_path_is_rel(filepath));
1974
1975 char tempname[FILE_MAX + 1];
1976
1977 eBLO_WritePathRemap remap_mode = params->remap_mode;
1978 const bool use_save_versions = params->use_save_versions;
1979 const bool use_save_as_copy = params->use_save_as_copy;
1980 const bool use_userdef = params->use_userdef;
1981 const BlendThumbnail *thumb = params->thumb;
1982 const bool relbase_valid = (mainvar->filepath[0] != '\0');
1983
1984 /* Extra protection: Never save a non asset file as asset file. Otherwise a normal file is turned
1985 * into an asset file, which can result in data loss because the asset system will allow editing
1986 * this file from the UI, regenerating its content with just the asset and it dependencies. */
1987 if ((write_flags & G_FILE_ASSET_EDIT_FILE) && !mainvar->is_asset_edit_file) {
1988 BKE_reportf(reports, RPT_ERROR, "Cannot save normal file (%s) as asset system file", tempname);
1989 return false;
1990 }
1991
1992 /* Path backup/restore. */
1993 void *path_list_backup = nullptr;
1996
1997 write_file_main_validate_pre(mainvar, reports);
1998
1999 /* Open temporary file, so we preserve the original in case we crash. */
2000 SNPRINTF(tempname, "%s@", filepath);
2001
2002 if (ww.open(tempname) == false) {
2004 reports, RPT_ERROR, "Cannot open file %s for writing: %s", tempname, strerror(errno));
2005 return false;
2006 }
2007
2008 if (remap_mode == BLO_WRITE_PATH_REMAP_ABSOLUTE) {
2009 /* Paths will already be absolute, no remapping to do. */
2010 if (relbase_valid == false) {
2011 remap_mode = BLO_WRITE_PATH_REMAP_NONE;
2012 }
2013 }
2014
2015 /* Remapping of relative paths to new file location. */
2016 if (remap_mode != BLO_WRITE_PATH_REMAP_NONE) {
2017 if (remap_mode == BLO_WRITE_PATH_REMAP_RELATIVE) {
2018 /* Make all relative as none of the existing paths can be relative in an unsaved document. */
2019 if (relbase_valid == false) {
2021 }
2022 }
2023
2024 /* The source path only makes sense to set if the file was saved (`relbase_valid`). */
2025 char dir_src[FILE_MAX];
2026 char dir_dst[FILE_MAX];
2027
2028 /* Normalize the paths in case there is some subtle difference (so they can be compared). */
2029 if (relbase_valid) {
2030 BLI_path_split_dir_part(mainvar->filepath, dir_src, sizeof(dir_src));
2031 BLI_path_normalize(dir_src);
2032 }
2033 else {
2034 dir_src[0] = '\0';
2035 }
2036 BLI_path_split_dir_part(filepath, dir_dst, sizeof(dir_dst));
2037 BLI_path_normalize(dir_dst);
2038
2039 /* Only for relative, not relative-all, as this means making existing paths relative. */
2040 if (remap_mode == BLO_WRITE_PATH_REMAP_RELATIVE) {
2041 if (relbase_valid && (BLI_path_cmp(dir_dst, dir_src) == 0)) {
2042 /* Saved to same path. Nothing to do. */
2043 remap_mode = BLO_WRITE_PATH_REMAP_NONE;
2044 }
2045 }
2046 else if (remap_mode == BLO_WRITE_PATH_REMAP_ABSOLUTE) {
2047 if (relbase_valid == false) {
2048 /* Unsaved, all paths are absolute.Even if the user manages to set a relative path,
2049 * there is no base-path that can be used to make it absolute. */
2050 remap_mode = BLO_WRITE_PATH_REMAP_NONE;
2051 }
2052 }
2053
2054 if (remap_mode != BLO_WRITE_PATH_REMAP_NONE) {
2055 /* Some path processing (e.g. with libraries) may use the current `main->filepath`, if this
2056 * is not matching the path currently used for saving, unexpected paths corruptions can
2057 * happen. See #98201. */
2058 char mainvar_filepath_orig[FILE_MAX];
2059 STRNCPY(mainvar_filepath_orig, mainvar->filepath);
2060 STRNCPY(mainvar->filepath, filepath);
2061
2062 /* Check if we need to backup and restore paths. */
2063 if (UNLIKELY(use_save_as_copy)) {
2064 path_list_backup = BKE_bpath_list_backup(mainvar, path_list_flag);
2065 }
2066
2067 switch (remap_mode) {
2069 /* Saved, make relative paths relative to new location (if possible). */
2070 BLI_assert(relbase_valid);
2071 BKE_bpath_relative_rebase(mainvar, dir_src, dir_dst, nullptr);
2072 break;
2074 /* Make all relative (when requested or unsaved). */
2075 BKE_bpath_relative_convert(mainvar, dir_dst, nullptr);
2076 break;
2078 /* Make all absolute (when requested or unsaved). */
2079 BLI_assert(relbase_valid);
2080 BKE_bpath_absolute_convert(mainvar, dir_src, nullptr);
2081 break;
2083 BLI_assert_unreachable(); /* Unreachable. */
2084 break;
2085 }
2086
2087 STRNCPY(mainvar->filepath, mainvar_filepath_orig);
2088 }
2089 }
2090
2091#if GENERATE_DEBUG_BLEND_FILE
2092 std::string debug_dst_path = blender::StringRef(filepath) + DEBUG_BLEND_FILE_SUFFIX;
2093 blender::fstream debug_dst_file(debug_dst_path, std::ios::out);
2094 std::ostream *debug_dst = &debug_dst_file;
2095#else
2096 std::ostream *debug_dst = nullptr;
2097#endif
2098
2099 /* Actual file writing. */
2100 const bool err = write_file_handle(
2101 mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb, debug_dst);
2102
2103 ww.close();
2104
2105 if (UNLIKELY(path_list_backup)) {
2106 BKE_bpath_list_restore(mainvar, path_list_flag, path_list_backup);
2107 BKE_bpath_list_free(path_list_backup);
2108 }
2109
2110 if (err) {
2111 BKE_report(reports, RPT_ERROR, strerror(errno));
2112 remove(tempname);
2113
2114 return false;
2115 }
2116
2117 /* File save to temporary file was successful, now do reverse file history
2118 * (move `.blend1` -> `.blend2`, `.blend` -> `.blend1` .. etc). */
2119 if (use_save_versions) {
2120 if (!do_history(filepath, reports)) {
2121 BKE_report(reports, RPT_ERROR, "Version backup failed (file saved with @)");
2122 return false;
2123 }
2124 }
2125
2126 if (BLI_rename_overwrite(tempname, filepath) != 0) {
2127 BKE_report(reports, RPT_ERROR, "Cannot change old file (file saved with @)");
2128 return false;
2129 }
2130
2131 write_file_main_validate_post(mainvar, reports);
2132 if (mainvar->is_global_main && !params->use_save_as_copy) {
2133 /* It is used to reload Blender after a crash on Windows OS. */
2134 STRNCPY(G.filepath_last_blend, filepath);
2135 }
2136 return true;
2137}
2138
2140
2141/* -------------------------------------------------------------------- */
2144
2145bool BLO_write_file(Main *mainvar,
2146 const char *filepath,
2147 const int write_flags,
2149 ReportList *reports)
2150{
2151 RawWriteWrap raw_wrap;
2152
2153 if (write_flags & G_FILE_COMPRESS) {
2154 ZstdWriteWrap zstd_wrap(raw_wrap);
2155 return BLO_write_file_impl(mainvar, filepath, write_flags, params, reports, zstd_wrap);
2156 }
2157
2158 return BLO_write_file_impl(mainvar, filepath, write_flags, params, reports, raw_wrap);
2159}
2160
2161bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, const int write_flags)
2162{
2163 bool use_userdef = false;
2164
2165 const bool err = write_file_handle(
2166 mainvar, nullptr, compare, current, write_flags, use_userdef, nullptr, nullptr);
2167
2168 return (err == 0);
2169}
2170
2171/*
2172 * API to write chunks of data.
2173 */
2174
2175void BLO_write_raw(BlendWriter *writer, const size_t size_in_bytes, const void *data_ptr)
2176{
2177 writedata(writer->wd, BLO_CODE_DATA, size_in_bytes, data_ptr);
2178}
2179
2180void BLO_write_struct_by_name(BlendWriter *writer, const char *struct_name, const void *data_ptr)
2181{
2182 BLO_write_struct_array_by_name(writer, struct_name, 1, data_ptr);
2183}
2184
2186 const char *struct_name,
2187 const int64_t array_size,
2188 const void *data_ptr)
2189{
2190 int struct_id = BLO_get_struct_id_by_name(writer, struct_name);
2191 if (UNLIKELY(struct_id == -1)) {
2192 CLOG_ERROR(&LOG, "Can't find SDNA code <%s>", struct_name);
2193 return;
2194 }
2195 BLO_write_struct_array_by_id(writer, struct_id, array_size, data_ptr);
2196}
2197
2198void BLO_write_struct_by_id(BlendWriter *writer, const int struct_id, const void *data_ptr)
2199{
2200 writestruct_nr(writer->wd, BLO_CODE_DATA, struct_id, 1, data_ptr);
2201}
2202
2204 const int struct_id,
2205 const void *address,
2206 const void *data_ptr)
2207{
2209 writer, BLO_CODE_DATA, struct_id, address, data_ptr);
2210}
2211
2213 const int filecode,
2214 const int struct_id,
2215 const void *address,
2216 const void *data_ptr)
2217{
2218 writestruct_at_address_nr(writer->wd, filecode, struct_id, 1, address, data_ptr);
2219}
2220
2222 const int struct_id,
2223 const int64_t array_size,
2224 const void *data_ptr)
2225{
2226 writestruct_nr(writer->wd, BLO_CODE_DATA, struct_id, array_size, data_ptr);
2227}
2228
2230 const int struct_id,
2231 const int64_t array_size,
2232 const void *address,
2233 const void *data_ptr)
2234{
2235 writestruct_at_address_nr(writer->wd, BLO_CODE_DATA, struct_id, array_size, address, data_ptr);
2236}
2237
2238void BLO_write_struct_list_by_id(BlendWriter *writer, const int struct_id, const ListBase *list)
2239{
2240 writelist_nr(writer->wd, BLO_CODE_DATA, struct_id, list);
2241}
2242
2243void BLO_write_struct_list_by_name(BlendWriter *writer, const char *struct_name, ListBase *list)
2244{
2245 int struct_id = BLO_get_struct_id_by_name(writer, struct_name);
2246 if (UNLIKELY(struct_id == -1)) {
2247 CLOG_ERROR(&LOG, "Can't find SDNA code <%s>", struct_name);
2248 return;
2249 }
2250 BLO_write_struct_list_by_id(writer, struct_id, list);
2251}
2252
2254 const int struct_id,
2255 const void *id_address,
2256 const ID *id)
2257{
2258 writestruct_at_address_nr(writer->wd, GS(id->name), struct_id, 1, id_address, id);
2259}
2260
2261int BLO_get_struct_id_by_name(const BlendWriter *writer, const char *struct_name)
2262{
2263 int struct_id = DNA_struct_find_with_alias(writer->wd->sdna, struct_name);
2264 return struct_id;
2265}
2266
2267void BLO_write_char_array(BlendWriter *writer, const int64_t num, const char *data_ptr)
2268{
2269 BLO_write_raw(writer, sizeof(char) * size_t(num), data_ptr);
2270}
2271
2272void BLO_write_int8_array(BlendWriter *writer, const int64_t num, const int8_t *data_ptr)
2273{
2274 BLO_write_raw(writer, sizeof(int8_t) * size_t(num), data_ptr);
2275}
2276
2277void BLO_write_int16_array(BlendWriter *writer, const int64_t num, const int16_t *data_ptr)
2278{
2279 BLO_write_raw(writer, sizeof(int16_t) * size_t(num), data_ptr);
2280}
2281
2282void BLO_write_uint8_array(BlendWriter *writer, const int64_t num, const uint8_t *data_ptr)
2283{
2284 BLO_write_raw(writer, sizeof(uint8_t) * size_t(num), data_ptr);
2285}
2286
2287void BLO_write_int32_array(BlendWriter *writer, const int64_t num, const int32_t *data_ptr)
2288{
2289 BLO_write_raw(writer, sizeof(int32_t) * size_t(num), data_ptr);
2290}
2291
2292void BLO_write_uint32_array(BlendWriter *writer, const int64_t num, const uint32_t *data_ptr)
2293{
2294 BLO_write_raw(writer, sizeof(uint32_t) * size_t(num), data_ptr);
2295}
2296
2297void BLO_write_float_array(BlendWriter *writer, const int64_t num, const float *data_ptr)
2298{
2299 BLO_write_raw(writer, sizeof(float) * size_t(num), data_ptr);
2300}
2301
2302void BLO_write_double_array(BlendWriter *writer, const int64_t num, const double *data_ptr)
2303{
2304 BLO_write_raw(writer, sizeof(double) * size_t(num), data_ptr);
2305}
2306
2307void BLO_write_pointer_array(BlendWriter *writer, const int64_t num, const void *data_ptr)
2308{
2309 /* Create a temporary copy of the pointer array, because all pointers need to be remapped to
2310 * their stable address ids. */
2312 reinterpret_cast<const void *const *>(data_ptr), num);
2313 for (const int64_t i : data.index_range()) {
2314 data[i] = get_address_id(*writer->wd, data[i]);
2315 }
2316
2317 writedata(writer->wd, BLO_CODE_DATA, data.data(), data.as_span().size_in_bytes(), data_ptr);
2318}
2319
2320void BLO_write_float3_array(BlendWriter *writer, const int64_t num, const float *data_ptr)
2321{
2322 BLO_write_raw(writer, sizeof(float[3]) * size_t(num), data_ptr);
2323}
2324
2325void BLO_write_string(BlendWriter *writer, const char *data_ptr)
2326{
2327 if (data_ptr != nullptr) {
2328 BLO_write_raw(writer, strlen(data_ptr) + 1, data_ptr);
2329 }
2330}
2331
2332void BLO_write_shared_tag(BlendWriter *writer, const void *data)
2333{
2334 if (!data) {
2335 return;
2336 }
2337 if (!BLO_write_is_undo(writer)) {
2338 return;
2339 }
2341 /* Check that the pointer has not been written before it was tagged as being shared. */
2343 address_id);
2344 writer->wd->stable_address_ids.pointer_map.add(data, address_id);
2345}
2346
2348 const void *data,
2349 const size_t approximate_size_in_bytes,
2350 const blender::ImplicitSharingInfo *sharing_info,
2351 const blender::FunctionRef<void()> write_fn)
2352{
2353 if (data == nullptr) {
2354 return;
2355 }
2356 if (sharing_info) {
2357 BLO_write_shared_tag(writer, data);
2358 }
2359 const uint64_t address_id = get_address_id_int(*writer->wd, data);
2360 if (BLO_write_is_undo(writer)) {
2361 MemFile &memfile = *writer->wd->mem.written_memfile;
2362 if (sharing_info != nullptr) {
2363 if (memfile.shared_storage == nullptr) {
2364 memfile.shared_storage = MEM_new<MemFileSharedStorage>(__func__);
2365 }
2366 if (memfile.shared_storage->sharing_info_by_address_id.add(address_id, {sharing_info, data}))
2367 {
2368 /* The undo-step takes (shared) ownership of the data, which also makes it immutable. */
2369 sharing_info->add_user();
2370 /* This size is an estimate, but good enough to count data with many users less. */
2371 memfile.size += approximate_size_in_bytes / sharing_info->strong_users();
2372 return;
2373 }
2374 }
2375 }
2376 if (sharing_info != nullptr) {
2377 if (!writer->wd->per_id_written_shared_addresses.add(data)) {
2378 /* Was written already. */
2379 return;
2380 }
2381 }
2382 write_fn();
2383}
2384
2386{
2387 return writer->wd->use_memfile;
2388}
2389
void BKE_asset_catalog_path_list_blend_write(BlendWriter *writer, const ListBase &catalog_path_list)
#define BLENDER_FILE_SUBVERSION
#define BLENDER_FILE_MIN_VERSION
#define BLENDER_FILE_VERSION
#define BLENDER_FILE_MIN_SUBVERSION
void BKE_bpath_relative_convert(Main *bmain, const char *basedir, ReportList *reports, BPathSummary *r_summary=nullptr)
Definition bpath.cc:618
void * BKE_bpath_list_backup(Main *bmain, eBPathForeachFlag flag)
Definition bpath.cc:692
void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *reports, BPathSummary *r_summary=nullptr)
Definition bpath.cc:627
void BKE_bpath_list_restore(Main *bmain, eBPathForeachFlag flag, void *path_list_handle)
Definition bpath.cc:706
void BKE_bpath_relative_rebase(Main *bmain, const char *basedir_src, const char *basedir_dst, ReportList *reports, BPathSummary *r_summary=nullptr)
Definition bpath.cc:478
eBPathForeachFlag
Definition BKE_bpath.hh:32
@ BKE_BPATH_FOREACH_PATH_SKIP_LINKED
Definition BKE_bpath.hh:40
@ BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE
Definition BKE_bpath.hh:69
void BKE_bpath_list_free(void *path_list_handle)
Definition bpath.cc:718
@ G_DEBUG_IO
#define G_FILE_FLAG_ALL_RUNTIME
@ G_FILE_ASSET_EDIT_FILE
@ G_FILE_RECOVER_WRITE
@ G_FILE_COMPRESS
void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop)
Definition idprop.cc:1461
const IDTypeInfo * BKE_idtype_get_info_from_id(const ID *id)
Definition idtype.cc:146
@ IDTYPE_FLAGS_NEVER_UNUSED
Definition BKE_idtype.hh:72
bool BKE_idtype_idcode_is_linkable(short idcode)
Definition idtype.cc:197
ViewLayer * BKE_view_layer_find(const Scene *scene, const char *layer_name)
void id_lib_extern(ID *id)
Definition lib_id.cc:290
#define MAIN_ID_SESSION_UID_UNSET
void id_lib_indirect_weak_link(ID *id)
Definition lib_id.cc:303
void BKE_main_id_refcount_recompute(Main *bmain, bool do_linked_only)
Definition lib_id.cc:2035
void BKE_lib_override_library_operations_create(Main *bmain, ID *local, int *r_report_flags)
@ IDWALK_RET_NOP
LibraryForeachIDCallbackFlag
@ IDWALK_CB_WRITEFILE_IGNORE
@ IDWALK_CB_DIRECT_WEAK_LINK
@ IDWALK_CB_INDIRECT_USAGE
void BKE_library_foreach_ID_link(Main *bmain, ID *id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, LibraryForeachIDFlag flag)
Definition lib_query.cc:431
@ IDWALK_INCLUDE_UI
@ IDWALK_READONLY
#define FOREACH_MAIN_ID_END
Definition BKE_main.hh:583
#define FOREACH_MAIN_ID_BEGIN(_bmain, _id)
Definition BKE_main.hh:577
bool BKE_main_namemap_validate_and_fix(Main &bmain)
void BKE_preferences_extension_repo_write_data(struct BlendWriter *writer, const bUserExtensionRepo *repo)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_DEBUG
Definition BKE_report.hh:34
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
bScreen * BKE_workspace_active_screen_get(const WorkSpaceInstanceHook *hook) GETTER_ATTRS
Definition workspace.cc:614
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:83
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define L_ENDIAN
#define ENDIAN_ORDER
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
#define O_BINARY
int BLI_rename_overwrite(const char *from, const char *to) ATTR_NONNULL()
Definition fileops_c.cc:528
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
File and directory operations.
#define LISTBASE_FOREACH(type, var, list)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
MINLINE int min_ii(int a, int b)
MINLINE int max_ii(int a, int b)
void copy_m3_m3(float m1[3][3], const float m2[3][3])
ATTR_WARN_UNUSED_RESULT const size_t num
#define FILE_MAX
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
bool BLI_path_is_abs_from_cwd(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
bool BLI_path_is_rel(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
#define BLI_path_cmp
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:604
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
unsigned char uchar
unsigned long ulong
void BLI_condition_notify_all(ThreadCondition *cond)
Definition threads.cc:595
void BLI_mutex_end(ThreadMutex *mutex)
Definition threads.cc:360
void BLI_threadpool_remove(struct ListBase *threadbase, void *callerdata)
Definition threads.cc:197
void BLI_condition_wait(ThreadCondition *cond, ThreadMutex *mutex)
Definition threads.cc:580
void BLI_threadpool_init(struct ListBase *threadbase, void *(*do_thread)(void *), int tot)
Definition threads.cc:121
void BLI_mutex_init(ThreadMutex *mutex)
Definition threads.cc:340
pthread_cond_t ThreadCondition
void BLI_condition_end(ThreadCondition *cond)
Definition threads.cc:600
int BLI_system_thread_count(void)
Definition threads.cc:253
void BLI_threadpool_end(struct ListBase *threadbase)
Definition threads.cc:234
void BLI_condition_init(ThreadCondition *cond)
Definition threads.cc:575
void BLI_mutex_lock(ThreadMutex *mutex)
Definition threads.cc:345
void BLI_mutex_unlock(ThreadMutex *mutex)
Definition threads.cc:350
int BLI_available_threads(struct ListBase *threadbase)
Definition threads.cc:146
void BLI_threadpool_insert(struct ListBase *threadbase, void *callerdata)
Definition threads.cc:184
pthread_mutex_t ThreadMutex
Definition BLI_threads.h:79
#define UNUSED_VARS_NDEBUG(...)
#define UNLIKELY(x)
#define POINTER_OFFSET(v, ofs)
Compatibility-like things for windows.
Utilities ensuring .blend file (i.e. Main) is in valid state during write and/or read process.
bool BLO_main_validate_libraries(Main *bmain, ReportList *reports)
bool BLO_main_validate_shapekeys(Main *bmain, ReportList *reports)
@ BLO_CODE_ENDB
@ BLO_CODE_REND
@ BLO_CODE_TEST
@ BLO_CODE_GLOB
@ BLO_CODE_DATA
@ BLO_CODE_DNA1
@ BLO_CODE_USER
#define SIZEOFBLENDERHEADER_VERSION_0
#define SIZEOFBLENDERHEADER_VERSION_1
#define BLEND_FILE_FORMAT_VERSION_1
#define SYSTEM_SUPPORTS_WRITING_FILE_VERSION_1
#define BLO_write_struct(writer, struct_name, data_ptr)
bool BLO_write_is_undo(BlendWriter *writer)
external readfile function prototypes.
#define BLEN_THUMB_MEMSIZE_FILE(_x, _y)
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
void BLO_memfile_write_finalize(MemFileWriteData *mem_data)
Definition undofile.cc:123
external writefile.cc function prototypes.
eBLO_WritePathRemap
@ BLO_WRITE_PATH_REMAP_NONE
@ BLO_WRITE_PATH_REMAP_RELATIVE_ALL
@ BLO_WRITE_PATH_REMAP_ABSOLUTE
@ BLO_WRITE_PATH_REMAP_RELATIVE
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
#define ID_IS_OVERRIDE_LIBRARY_VIRTUAL(_id)
Definition DNA_ID.h:727
#define ID_IS_PACKED(_id)
Definition DNA_ID.h:700
@ ID_TAG_NO_USER_REFCOUNT
Definition DNA_ID.h:1018
@ ID_TAG_INDIRECT
Definition DNA_ID.h:848
@ ID_TAG_RUNTIME
Definition DNA_ID.h:859
@ ID_TAG_EXTERN
Definition DNA_ID.h:842
@ ID_TAG_NOT_ALLOCATED
Definition DNA_ID.h:1025
@ ID_TAG_NO_MAIN
Definition DNA_ID.h:978
#define ID_IS_OVERRIDE_LIBRARY_REAL(_id)
Definition DNA_ID.h:723
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:694
#define MAX_ID_NAME
Definition DNA_ID.h:373
#define ID_TAG_KEEP_ON_UNDO
Definition DNA_ID.h:1046
@ ID_FLAG_INDIRECT_WEAK_LINK
Definition DNA_ID.h:780
@ ID_FLAG_EMBEDDED_DATA
Definition DNA_ID.h:774
@ LIBRARY_FLAG_IS_ARCHIVE
Definition DNA_ID.h:593
@ ID_LI
@ ID_KE
@ ID_SCE
#define ID_LINK_PLACEHOLDER
blenloader genfile private function prototypes
const struct SDNA * DNA_sdna_current_get(void)
int DNA_struct_find_with_alias(const struct SDNA *sdna, const char *str)
int DNA_struct_size(const struct SDNA *sdna, int struct_index)
@ R_BG_RENDER
#define SDNA_RAW_DATA_STRUCT_INDEX
#define USER_DEVELOPER_TOOL_TEST(userdef, member)
@ USER_MENU_TYPE_OPERATOR
@ USER_MENU_TYPE_PROP
@ USER_MENU_TYPE_MENU
Read Guarded memory(de)allocation.
#define U
GHash * id_hash
BMesh const char void * data
char build_hash[]
Definition bpy_app.cc:70
ulong build_commit_timestamp
Definition bpy_app.cc:67
long long int int64_t
unsigned long long int uint64_t
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
Value & lookup_or_add_cb(const Key &key, const CreateValueF &create_value)
Definition BLI_map.hh:620
bool contains(const Key &key) const
Definition BLI_map.hh:353
bool write(const void *buf, size_t buf_len) override
Definition writefile.cc:216
bool open(const char *filepath) override
Definition writefile.cc:199
bool close() override
Definition writefile.cc:212
bool add(const Key &key)
Definition BLI_set.hh:248
bool is_empty() const
Definition BLI_set.hh:595
void clear()
Definition BLI_set.hh:551
virtual bool open(const char *filepath)=0
virtual bool write(const void *buf, size_t buf_len)=0
bool use_buf
Definition writefile.cc:186
virtual bool close()=0
ZstdWriteWrap(WriteWrap &base_wrap)
Definition writefile.cc:236
bool open(const char *filepath) override
Definition writefile.cc:302
bool close() override
Definition writefile.cc:358
bool write(const void *buf, size_t buf_len) override
Definition writefile.cc:372
Span< Value > lookup(const Key &key) const
void add(const Key &key, const Value &value)
void append(const T &value)
bool is_empty() const
#define GS(x)
#define INT32_MAX
ThreadMutex mutex
#define sizeof
#define printf(...)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
#define LOG(level)
Definition log.h:97
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
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
#define G(x, y, z)
void print_structs_at_address(const SDNA &sdna, int struct_id, const void *data, const void *address, int64_t element_num, std::ostream &stream)
int sdna_struct_id_get_max()
const char * name
int64_t len
int64_t nr
const void * old
BLO_Write_IDBuffer(ID &id, bool is_undo, bool is_placeholder)
WriteData * wd
Definition writefile.cc:512
struct ViewLayer * cur_view_layer
struct Scene * curscene
float colorspace_scene_linear_to_xyz[3][3]
uint64_t build_commit_timestamp
char colorspace_scene_linear_name[64]
struct bScreen * curscreen
char filepath[1024]
IDTypeBlendWriteFunction blend_write
uint32_t flags
size_t struct_size
Definition DNA_ID.h:414
void * py_instance
Definition DNA_ID.h:518
int tag
Definition DNA_ID.h:442
ID_RuntimeHandle * runtime
Definition DNA_ID.h:538
struct Library * lib
Definition DNA_ID.h:420
char name[258]
Definition DNA_ID.h:432
int us
Definition DNA_ID.h:443
int icon_id
Definition DNA_ID.h:444
struct ID * newid
Definition DNA_ID.h:418
void * prev
Definition DNA_ID.h:417
struct ID * orig_id
Definition DNA_ID.h:501
short flag
Definition DNA_ID.h:438
void * next
Definition DNA_ID.h:417
IDHash deep_hash
Definition DNA_ID.h:474
unsigned int recalc_up_to_undo_push
Definition DNA_ID.h:455
unsigned int session_uid
Definition DNA_ID.h:462
ID * from
LibraryForeachIDCallbackFlag cb_flag
struct PackedFile * packedfile
Definition DNA_ID.h:572
ID id
Definition DNA_ID.h:550
uint16_t flag
Definition DNA_ID.h:555
LibraryRuntimeHandle * runtime
Definition DNA_ID.h:579
void * first
char scene_linear_name[64]
Definition BKE_main.hh:150
blender::float3x3 scene_linear_to_xyz
Definition BKE_main.hh:151
ListBase scenes
Definition BKE_main.hh:278
ListBase wm
Definition BKE_main.hh:307
bool is_asset_edit_file
Definition BKE_main.hh:199
char filepath[1024]
Definition BKE_main.hh:179
ListBase libraries
Definition BKE_main.hh:279
MainLock * lock
Definition BKE_main.hh:343
bool is_global_main
Definition BKE_main.hh:239
MainColorspace colorspace
Definition BKE_main.hh:274
blender::Map< uint64_t, blender::ImplicitSharingInfoAndData > sharing_info_by_address_id
blender::Map< uint, MemFileChunk * > id_session_uid_mapping
MemFile * written_memfile
MemFileChunk * reference_current_chunk
MemFileSharedStorage * shared_storage
size_t size
char scene_name[MAX_ID_NAME - 2]
const char * data
int data_size
struct ListBase addons
ListBase script_directories
struct ListBase uistyles
struct ListBase asset_shelves_settings
struct ListBase user_keymaps
struct ListBase themes
struct ListBase autoexec_paths
struct ListBase user_keyconfig_prefs
struct ListBase extension_repos
struct ListBase user_menus
struct ListBase asset_libraries
blender::Set< const void * > per_id_addresses_set
Definition writefile.cc:449
const SDNA * sdna
Definition writefile.cc:415
struct WriteData::@204076211151137013032026070275231302221043373223 validation_data
WriteWrap * ww
Definition writefile.cc:508
uint64_t next_id_hint
Definition writefile.cc:489
std::unique_ptr< blender::dna::pointers::PointersInDNA > sdna_pointers
Definition writefile.cc:457
uchar * buf
Definition writefile.cc:420
bool critical_error
Definition writefile.cc:444
bool use_memfile
Definition writefile.cc:501
blender::Map< const void *, uint64_t > pointer_map
Definition writefile.cc:467
struct WriteData::@150011202103324140201313267175203141004052371337 buffer
blender::Set< const void * > per_id_written_shared_addresses
Definition writefile.cc:496
bool is_writing_id
Definition writefile.cc:436
blender::Set< uint64_t > used_ids
Definition writefile.cc:474
std::ostream * debug_dst
Definition writefile.cc:416
MemFileWriteData mem
Definition writefile.cc:499
struct WriteData::@371046243164336051014002034275316217273200361171 stable_address_ids
size_t chunk_size
Definition writefile.cc:427
size_t max_size
Definition writefile.cc:425
size_t used_len
Definition writefile.cc:422
uint32_t uncompressed_size
Definition writefile.cc:176
uint32_t compressed_size
Definition writefile.cc:175
ZstdFrame * next
Definition writefile.cc:173
ZstdFrame * prev
Definition writefile.cc:173
static void * write_task(void *userdata)
Definition writefile.cc:256
struct IDProperty * prop
const c_style_mat & ptr() const
struct Scene * scene
struct wmWindow * next
struct WorkSpaceInstanceHook * workspace_hook
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
uint len
static DynamicLibrary lib
void BLO_write_double_array(BlendWriter *writer, const int64_t num, const double *data_ptr)
void BLO_write_uint32_array(BlendWriter *writer, const int64_t num, const uint32_t *data_ptr)
static void writedata_do_write(WriteData *wd, const void *mem, const size_t memlen)
Definition writefile.cc:540
static void writestruct_at_address_nr(WriteData *wd, const int filecode, const int struct_nr, const int64_t nr, const void *adr, const void *data)
Definition writefile.cc:931
void blo_write_id_struct(BlendWriter *writer, const int struct_id, const void *id_address, const ID *id)
int BLO_get_struct_id_by_name(const BlendWriter *writer, const char *struct_name)
static void mywrite_id_begin(WriteData *wd, ID *id)
Definition writefile.cc:733
#define ZSTD_COMPRESSION_LEVEL
Definition writefile.cc:161
static uint64_t get_next_stable_address_id(WriteData &wd, uint64_t &hint)
Definition writefile.cc:880
static void prepare_stable_data_block_ids(WriteData &wd, Main &bmain)
static void write_thumb(WriteData *wd, const BlendThumbnail *thumb)
#define writestruct(wd, filecode, struct_id, nr, adr)
static uint64_t get_address_id_int(WriteData &wd, const void *address)
Definition writefile.cc:915
static void current_screen_compat(Main *mainvar, const bool use_active_win, bScreen **r_screen, Scene **r_scene, ViewLayer **r_view_layer)
static uint64_t get_address_id_for_implicit_sharing_data(const void *data)
Definition writefile.cc:903
void BLO_write_pointer_array(BlendWriter *writer, const int64_t num, const void *data_ptr)
void BLO_write_struct_array_by_name(BlendWriter *writer, const char *struct_name, const int64_t array_size, const void *data_ptr)
void BLO_write_struct_at_address_by_id(BlendWriter *writer, const int struct_id, const void *address, const void *data_ptr)
static bool write_file_handle(Main *mainvar, WriteWrap *ww, MemFile *compare, MemFile *current, const int write_flags, const bool use_userdef, const BlendThumbnail *thumb, std::ostream *debug_dst)
static uint64_t get_stable_pointer_hint_for_id(const ID &id, const bool is_undo)
Definition writefile.cc:688
static void writelist_nr(WriteData *wd, const int filecode, const int struct_nr, const ListBase *lb)
static std::string get_blend_file_header()
void BLO_write_int32_array(BlendWriter *writer, const int64_t num, const int32_t *data_ptr)
void BLO_write_struct_by_name(BlendWriter *writer, const char *struct_name, const void *data_ptr)
static void writedata_free(WriteData *wd)
Definition writefile.cc:566
#define DEBUG_BLEND_FILE_SUFFIX
Definition writefile.cc:150
static void write_keymapitem(BlendWriter *writer, const wmKeyMapItem *kmi)
static void write_id(WriteData *wd, ID *id)
#define ZSTD_CHUNK_SIZE
Definition writefile.cc:159
static bool do_history(const char *filepath, ReportList *reports)
static void write_renderinfo(WriteData *wd, Main *mainvar)
void BLO_write_struct_list_by_id(BlendWriter *writer, const int struct_id, const ListBase *list)
static void write_userdef(BlendWriter *writer, const UserDef *userdef)
#define writestruct_at_address(wd, filecode, struct_id, nr, adr, data)
static void write_file_main_validate_pre(Main *bmain, ReportList *reports)
void BLO_write_float_array(BlendWriter *writer, const int64_t num, const float *data_ptr)
static void writestruct_nr(WriteData *wd, const int filecode, const int struct_nr, const int64_t nr, const void *adr)
static void write_file_main_validate_post(Main *bmain, ReportList *reports)
void BLO_write_struct_by_id(BlendWriter *writer, const int struct_id, const void *data_ptr)
#define MEM_CHUNK_SIZE
Definition writefile.cc:156
void BLO_write_struct_array_at_address_by_id(BlendWriter *writer, const int struct_id, const int64_t array_size, const void *address, const void *data_ptr)
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
static WriteData * writedata_new(WriteWrap *ww)
Definition writefile.cc:515
void BLO_write_int8_array(BlendWriter *writer, const int64_t num, const int8_t *data_ptr)
void BLO_write_int16_array(BlendWriter *writer, const int64_t num, const int16_t *data_ptr)
static void writedata(WriteData *wd, const int filecode, const void *data, const size_t len, const void *adr)
void BLO_write_uint8_array(BlendWriter *writer, const int64_t num, const uint8_t *data_ptr)
static void write_libraries(WriteData *wd, Main *bmain)
static void write_global(WriteData *wd, const int fileflags, Main *mainvar)
static bool write_at_address_validate(WriteData *wd, const int filecode, const void *address)
Definition writefile.cc:808
void BLO_write_char_array(BlendWriter *writer, const int64_t num, const char *data_ptr)
static bool mywrite_end(WriteData *wd)
Definition writefile.cc:671
static void mywrite_flush(WriteData *wd)
Definition writefile.cc:584
constexpr uint64_t implicit_sharing_address_id_flag
Definition writefile.cc:864
static blender::Vector< ID * > gather_local_ids_to_write(Main *bmain, const bool is_undo)
void BLO_write_shared_tag(BlendWriter *writer, const void *data)
static void mywrite_id_end(WriteData *wd, ID *)
Definition writefile.cc:779
static void write_blend_file_header(WriteData *wd)
#define ZSTD_BUFFER_SIZE
Definition writefile.cc:158
static uint64_t stable_id_from_hint(const uint64_t hint)
Definition writefile.cc:866
bool BLO_write_file(Main *mainvar, const char *filepath, const int write_flags, const BlendFileWriteParams *params, ReportList *reports)
static void write_id_placeholder(WriteData *wd, ID *id)
void BLO_write_struct_array_by_id(BlendWriter *writer, const int struct_id, const int64_t array_size, const void *data_ptr)
bool BLO_write_is_undo(BlendWriter *writer)
#define MEM_BUFFER_SIZE
Definition writefile.cc:155
static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_data)
static void mywrite(WriteData *wd, const void *adr, size_t len)
Definition writefile.cc:597
void BLO_write_raw(BlendWriter *writer, const size_t size_in_bytes, const void *data_ptr)
static const void * get_address_id(WriteData &wd, const void *address)
Definition writefile.cc:926
bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, const int write_flags)
static WriteData * mywrite_begin(WriteWrap *ww, MemFile *compare, MemFile *current)
Definition writefile.cc:653
void BLO_write_struct_list_by_name(BlendWriter *writer, const char *struct_name, ListBase *list)
static void write_bhead(WriteData *wd, const BHead &bhead)
Definition writefile.cc:826
static void write_raw_data_in_debug_file(WriteData *wd, const size_t len, const void *address_id, const void *data)
static bool BLO_write_file_impl(Main *mainvar, const char *filepath, const int write_flags, const BlendFileWriteParams *params, ReportList *reports, WriteWrap &ww)
void BLO_write_shared(BlendWriter *writer, const void *data, const size_t approximate_size_in_bytes, const blender::ImplicitSharingInfo *sharing_info, const blender::FunctionRef< void()> write_fn)
void BLO_write_struct_at_address_by_id_with_filecode(BlendWriter *writer, const int filecode, const int struct_id, const void *address, const void *data_ptr)
void BLO_write_float3_array(BlendWriter *writer, const int64_t num, const float *data_ptr)