Files
test/source/blender/blenloader/BLO_read_write.hh
Jacques Lucke 9fd7a093c9 DNA: support getting sdna id for static DNA type
This adds a new `DNA_sdna_type_ids.hh` header:
```cpp
namespace blender::dna {

/**
 * Each DNA struct has an integer identifier which is unique within a specific
 * Blender build, but not necessarily across different builds. The identifier
 * can be used to index into `SDNA.structs`.
 */
template<typename T> int sdna_struct_id_get();

/**
 * The maximum identifier that will be returned by #sdna_struct_id_get in this
 * Blender build.
 */
int sdna_struct_id_get_max();

}  // namespace blender::dna
```

The `sdna_struct_id_get` function is used as replacement of
`SDNA_TYPE_FROM_STRUCT` in all places except the DNA defaults system. The
defaults system is C code and therefore can't use the template. There is ongoing
work to replace the defaults system as well though: #134531.

Using this templated function has some benefits over the old approach:
* No need to rely on macros.
* Can use type inferencing in functions like `BLO_write_struct` which avoids
  redundancy on the call site. E.g. `BLO_write_struct(writer, ActionStrip,
  strip);` can become `BLO_write_struct(writer, strip);` which could even become
  `writer.write_struct(strip);`. None of that is implemented as part of this
  patch though.
* No need to include the generated `dna_type_offsets.h` file which contains a
  huge enum.

Implementation wise, this is done using explicit template instantiations in a
new file generated by `makesdna.cc`: `dna_struct_ids.cc`. The generated file
looks like so:
```cpp
namespace blender::dna {

template<typename T> int sdna_struct_id_get();

int sdna_struct_id_get_max();
int sdna_struct_id_get_max() { return 951; }

}
struct IDPropertyUIData;
template<> int blender:🧬:sdna_struct_id_get<IDPropertyUIData>() { return 1; }
struct IDPropertyUIDataEnumItem;
template<> int blender:🧬:sdna_struct_id_get<IDPropertyUIDataEnumItem>() { return 2; }
```

I tried using static variables instead of separate functions, but I didn't
manage to link it properly. Not quite sure yet if that's an actual limitation or
if I was just missing something.

Pull Request: https://projects.blender.org/blender/blender/pulls/138706
2025-05-12 11:16:26 +02:00

439 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup blenloader
*
* This file contains an API that allows different parts of Blender to define what data is stored
* in .blend files.
*
* Four callbacks have to be provided to fully implement .blend I/O for a piece of data. One of
* those is related to file writing and three for file reading. Reading requires multiple
* callbacks, due to the way linking between files works.
*
* Brief description of the individual callbacks:
* - Blend Write: Define which structs and memory buffers are saved.
* - Blend Read Data: Loads structs and memory buffers from file and updates pointers them.
* - Blend Read Lib: Updates pointers to ID data blocks.
* - Blend Expand: Defines which other data blocks should be loaded (possibly from other files).
* Note, this is now handled as part of the foreach-id iteration. This needs to be implemented
* for DNA data that has references to data-blocks.
*
* Each of these callbacks uses a different API functions.
*
* Some parts of Blender, e.g. modifiers, don't require you to implement all four callbacks.
* Instead only the first two are necessary. The other two are handled by general ID management. In
* the future, we might want to get rid of those two callbacks entirely, but for now they are
* necessary.
*/
#pragma once
#include "DNA_sdna_type_ids.hh"
#include "DNA_windowmanager_types.h" /* for eReportType */
#include "BLI_function_ref.hh"
#include "BLI_implicit_sharing.hh"
#include "BLI_memory_utils.hh"
namespace blender {
class ImplicitSharingInfo;
}
struct BlendDataReader;
struct BlendFileReadReport;
struct BlendLibReader;
struct BlendWriter;
struct Main;
/* -------------------------------------------------------------------- */
/** \name Blend Write API
*
* Most functions fall into one of two categories. Either they write a DNA struct or a raw memory
* buffer to the .blend file.
*
* It is safe to pass NULL as data_ptr. In this case nothing will be stored.
*
* DNA Struct Writing
* ------------------
*
* Functions dealing with DNA structs begin with `BLO_write_struct_*`.
*
* DNA struct types can be identified in different ways:
* - Run-time Name: The name is provided as `const char *`.
* - Compile-time Name: The name is provided at compile time. This is more efficient.
* - Struct ID: Every DNA struct type has an integer ID that can be queried with
* #BLO_get_struct_id_by_name. Providing this ID can be a useful optimization when many
* structs of the same type are stored AND if those structs are not in a continuous array.
*
* Often only a single instance of a struct is written at once. However, sometimes it is necessary
* to write arrays or linked lists. Separate functions for that are provided as well.
*
* There is a special macro for writing id structs: #BLO_write_id_struct.
* Those are handled differently from other structs.
*
* Raw Data Writing
* ----------------
*
* At the core there is #BLO_write_raw, which can write arbitrary memory buffers to the file.
* The code that reads this data might have to correct its byte-order. For the common cases
* there are convenience functions that write and read arrays of simple types such as `int32`.
* Those will correct endianness automatically.
* \{ */
/**
* Mapping between names and ids.
*/
int BLO_get_struct_id_by_name(const BlendWriter *writer, const char *struct_name);
/**
* Write single struct.
*/
void BLO_write_struct_by_name(BlendWriter *writer, const char *struct_name, const void *data_ptr);
void BLO_write_struct_by_id(BlendWriter *writer, int struct_id, const void *data_ptr);
#define BLO_write_struct(writer, struct_name, data_ptr) \
BLO_write_struct_by_id(writer, blender::dna::sdna_struct_id_get<struct_name>(), data_ptr)
/**
* Write single struct at address.
*/
void BLO_write_struct_at_address_by_id(BlendWriter *writer,
int struct_id,
const void *address,
const void *data_ptr);
#define BLO_write_struct_at_address(writer, struct_name, address, data_ptr) \
BLO_write_struct_at_address_by_id( \
writer, blender::dna::sdna_struct_id_get<struct_name>(), address, data_ptr)
/**
* Write single struct at address and specify a file-code.
*/
void BLO_write_struct_at_address_by_id_with_filecode(
BlendWriter *writer, int filecode, int struct_id, const void *address, const void *data_ptr);
#define BLO_write_struct_at_address_with_filecode( \
writer, filecode, struct_name, address, data_ptr) \
BLO_write_struct_at_address_by_id_with_filecode( \
writer, filecode, blender::dna::sdna_struct_id_get<struct_name>(), address, data_ptr)
/**
* Write struct array.
*/
void BLO_write_struct_array_by_name(BlendWriter *writer,
const char *struct_name,
int64_t array_size,
const void *data_ptr);
void BLO_write_struct_array_by_id(BlendWriter *writer,
int struct_id,
int64_t array_size,
const void *data_ptr);
#define BLO_write_struct_array(writer, struct_name, array_size, data_ptr) \
BLO_write_struct_array_by_id( \
writer, blender::dna::sdna_struct_id_get<struct_name>(), array_size, data_ptr)
/**
* Write struct array at address.
*/
void BLO_write_struct_array_at_address_by_id(BlendWriter *writer,
int struct_id,
int64_t array_size,
const void *address,
const void *data_ptr);
#define BLO_write_struct_array_at_address(writer, struct_name, array_size, address, data_ptr) \
BLO_write_struct_array_at_address_by_id( \
writer, blender::dna::sdna_struct_id_get<struct_name>(), array_size, address, data_ptr)
/**
* Write struct list.
*/
void BLO_write_struct_list_by_name(BlendWriter *writer, const char *struct_name, ListBase *list);
void BLO_write_struct_list_by_id(BlendWriter *writer, int struct_id, const ListBase *list);
#define BLO_write_struct_list(writer, struct_name, list_ptr) \
BLO_write_struct_list_by_id(writer, blender::dna::sdna_struct_id_get<struct_name>(), list_ptr)
/**
* Write id struct.
*/
void blo_write_id_struct(BlendWriter *writer, int struct_id, const void *id_address, const ID *id);
#define BLO_write_id_struct(writer, struct_name, id_address, id) \
blo_write_id_struct(writer, blender::dna::sdna_struct_id_get<struct_name>(), id_address, id)
/**
* Specific code to prepare IDs to be written.
*
* Required for writing properly embedded IDs currently.
*
* \note Once there is a better generic handling of embedded IDs,
* this may go back to private code in `writefile.cc`.
*/
struct BLO_Write_IDBuffer {
private:
static constexpr int static_size = 8192;
blender::DynamicStackBuffer<static_size> buffer_;
public:
BLO_Write_IDBuffer(ID &id, bool is_undo);
BLO_Write_IDBuffer(ID &id, BlendWriter *writer);
ID *get()
{
return static_cast<ID *>(buffer_.buffer());
};
};
/**
* Write raw data.
*
* \warning Avoid using this function if possible. There are only a very few cases in current code
* where it is actually needed (e.g. the ShapeKey's data, since its items size varies depending on
* the type of geometry owning it, see #shapekey_blend_write).
*
* \warning Data written with this call have no type information attached to them
* in the blend-file. The main consequence is that there will be no handling of endianness
* conversion for them in readfile code.
* Basic types array functions (like #BLO_write_int8_array etc.) also use #BLO_write_raw
* internally, but if their matching read function is used to load the data (like
* #BLO_read_int8_array), the read function will take care of endianness conversion.
*/
void BLO_write_raw(BlendWriter *writer, size_t size_in_bytes, const void *data_ptr);
/**
* Slightly 'safer' code to write arrays of basic types data.
*/
void BLO_write_char_array(BlendWriter *writer, int64_t num, const char *data_ptr);
void BLO_write_int8_array(BlendWriter *writer, int64_t num, const int8_t *data_ptr);
void BLO_write_int16_array(BlendWriter *writer, int64_t num, const int16_t *data_ptr);
void BLO_write_uint8_array(BlendWriter *writer, int64_t num, const uint8_t *data_ptr);
void BLO_write_int32_array(BlendWriter *writer, int64_t num, const int32_t *data_ptr);
void BLO_write_uint32_array(BlendWriter *writer, int64_t num, const uint32_t *data_ptr);
void BLO_write_float_array(BlendWriter *writer, int64_t num, const float *data_ptr);
void BLO_write_double_array(BlendWriter *writer, int64_t num, const double *data_ptr);
void BLO_write_float3_array(BlendWriter *writer, int64_t num, const float *data_ptr);
void BLO_write_pointer_array(BlendWriter *writer, int64_t num, const void *data_ptr);
/**
* Write a null terminated string.
*/
void BLO_write_string(BlendWriter *writer, const char *data_ptr);
/* Misc. */
/**
* Check if the data can be written more efficiently by making use of implicit-sharing. If yes, the
* user count of the sharing-info is increased making the data immutable. The provided callback
* should serialize the potentially shared data. It is only called when necessary.
*
* \param approximate_size_in_bytes: Used to be able to approximate how large the undo step is in
* total.
* \param write_fn: Use the #BlendWrite to serialize the potentially shared data.
*/
void BLO_write_shared(BlendWriter *writer,
const void *data,
size_t approximate_size_in_bytes,
const blender::ImplicitSharingInfo *sharing_info,
blender::FunctionRef<void()> write_fn);
/**
* Sometimes different data is written depending on whether the file is saved to disk or used for
* undo. This function returns true when the current file-writing is done for undo.
*/
bool BLO_write_is_undo(BlendWriter *writer);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blend Read Data API
*
* Generally, for every BLO_write_* call there should be a corresponding BLO_read_* call.
*
* Most BLO_read_* functions get a pointer to a pointer as argument. That allows the function to
* update the pointer to its new value.
*
* When the given pointer points to a memory buffer that was not stored in the file, the pointer is
* updated to be NULL. When it was pointing to NULL before, it will stay that way.
*
* Examples of matching calls:
*
* \code{.c}
* BLO_write_struct(writer, ClothSimSettings, clmd->sim_parms);
* BLO_read_struct(reader, ClothSimSettings, &clmd->sim_parms);
*
* BLO_write_struct_list(writer, TimeMarker, &action->markers);
* BLO_read_struct_list(reader, TimeMarker, &action->markers);
*
* BLO_write_int32_array(writer, hmd->totindex, hmd->indexar);
* BLO_read_int32_array(reader, hmd->totindex, &hmd->indexar);
* \endcode
*
* Avoid using the generic #BLO_read_data_address
* (and low-level API like #BLO_read_get_new_data_address)
* when possible, use the typed functions instead.
* Only data written with #BLO_write_raw should typically be read with #BLO_read_data_address.
* \{ */
void *BLO_read_get_new_data_address(BlendDataReader *reader, const void *old_address);
#define BLO_read_data_address(reader, ptr_p) \
*((void **)ptr_p) = BLO_read_get_new_data_address((reader), *(ptr_p))
/**
* Does not consider the read data as 'used'. It will still be freed by readfile code at the
* end of the reading process, if no other 'real' usage was detected for it.
*
* Typical valid usages include:
* - Restoring pointers to a specific item in an array or list (usually 'active' item e.g.). The
* found item is expected to also be read as part of its array/list storage reading.
* - Doing temporary access to deprecated data as part of some versioning code.
*/
void *BLO_read_get_new_data_address_no_us(BlendDataReader *reader,
const void *old_address,
size_t expected_size);
/**
* The 'main' read function and helper macros for non-basic data types.
*
* NOTE: Currently the usage of the type info is very minimal/basic, it merely does a lose check on
* the data size.
*/
void *BLO_read_struct_array_with_size(BlendDataReader *reader,
const void *old_address,
size_t expected_size);
#define BLO_read_struct(reader, struct_name, ptr_p) \
*((void **)ptr_p) = BLO_read_struct_array_with_size( \
reader, *((void **)ptr_p), sizeof(struct_name))
#define BLO_read_struct_array(reader, struct_name, array_size, ptr_p) \
*((void **)ptr_p) = BLO_read_struct_array_with_size( \
reader, *((void **)ptr_p), sizeof(struct_name) * (array_size))
/**
* Similar to #BLO_read_struct_array_with_size, but can use a (DNA) type name instead of the type
* itself to find the expected data size.
*
* Somewhat mirrors #BLO_write_struct_array_by_name.
*/
void *BLO_read_struct_by_name_array(BlendDataReader *reader,
const char *struct_name,
int64_t items_num,
const void *old_address);
/* Read all elements in list
*
* Updates all `->prev` and `->next` pointers of the list elements.
* Updates the `list->first` and `list->last` pointers.
*/
void BLO_read_struct_list_with_size(BlendDataReader *reader,
size_t expected_elem_size,
ListBase *list);
#define BLO_read_struct_list(reader, struct_name, list) \
BLO_read_struct_list_with_size(reader, sizeof(struct_name), list)
/* Update data pointers and correct byte-order if necessary. */
void BLO_read_char_array(BlendDataReader *reader, int64_t array_size, char **ptr_p);
void BLO_read_int8_array(BlendDataReader *reader, int64_t array_size, int8_t **ptr_p);
void BLO_read_uint8_array(BlendDataReader *reader, int64_t array_size, uint8_t **ptr_p);
void BLO_read_int16_array(BlendDataReader *reader, const int64_t array_size, int16_t **ptr_p);
void BLO_read_int32_array(BlendDataReader *reader, int64_t array_size, int32_t **ptr_p);
void BLO_read_uint32_array(BlendDataReader *reader, int64_t array_size, uint32_t **ptr_p);
void BLO_read_float_array(BlendDataReader *reader, int64_t array_size, float **ptr_p);
void BLO_read_float3_array(BlendDataReader *reader, int64_t array_size, float **ptr_p);
void BLO_read_double_array(BlendDataReader *reader, int64_t array_size, double **ptr_p);
void BLO_read_pointer_array(BlendDataReader *reader, int64_t array_size, void **ptr_p);
/* Read null terminated string. */
void BLO_read_string(BlendDataReader *reader, char **ptr_p);
void BLO_read_string(BlendDataReader *reader, char *const *ptr_p);
void BLO_read_string(BlendDataReader *reader, const char **ptr_p);
/* Misc. */
blender::ImplicitSharingInfoAndData blo_read_shared_impl(
BlendDataReader *reader,
const void **ptr_p,
blender::FunctionRef<const blender::ImplicitSharingInfo *()> read_fn);
/**
* Check if there is any shared data for the given data pointer. If yes, return the existing
* sharing-info. If not, call the provided function to actually read the data now.
*/
template<typename T>
const blender::ImplicitSharingInfo *BLO_read_shared(
BlendDataReader *reader,
T **data_ptr,
blender::FunctionRef<const blender::ImplicitSharingInfo *()> read_fn)
{
blender::ImplicitSharingInfoAndData shared_data = blo_read_shared_impl(
reader, (const void **)data_ptr, read_fn);
/* Need const-cast here, because not all DNA members that reference potentially shared data are
* const yet. */
*data_ptr = const_cast<T *>(static_cast<const T *>(shared_data.data));
return shared_data.sharing_info;
}
int BLO_read_fileversion_get(BlendDataReader *reader);
bool BLO_read_requires_endian_switch(BlendDataReader *reader);
bool BLO_read_data_is_undo(BlendDataReader *reader);
void BLO_read_data_globmap_add(BlendDataReader *reader, void *oldaddr, void *newaddr);
void BLO_read_glob_list(BlendDataReader *reader, ListBase *list);
BlendFileReadReport *BLO_read_data_reports(BlendDataReader *reader);
struct Library *BLO_read_data_current_library(BlendDataReader *reader);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blend Read Lib API
*
* This API does almost the same as the Blend Read Data API.
* However, now only pointers to ID data blocks are updated.
* \{ */
/**
* Search for the new address of given `id`,
* during library linking part of blend-file reading process.
*
* \param self_id: the ID owner of the given `id` pointer. Note that it may be an embedded ID.
* \param is_linked_only: If `true`, only return found pointer if it is a linked ID. Used to
* prevent linked data to point to local IDs.
* \return the new address of the given ID pointer, or null if not found.
*/
ID *BLO_read_get_new_id_address(BlendLibReader *reader,
ID *self_id,
const bool is_linked_only,
ID *id) ATTR_NONNULL(2);
/**
* Search for the new address of the ID for the given `session_uid`.
*
* Only IDs existing in the newly read Main will be returned. If no matching `session_uid` in new
* main can be found, `nullptr` is returned.
*
* This expected to be used during library-linking and/or 'undo_preserve' processes in undo case
* (i.e. memfile reading), typically to find a valid value (or nullptr) for ID pointers values
* coming from the previous, existing Main data, when it is preserved in newly read Main.
* See e.g. the #scene_undo_preserve code-path.
*/
ID *BLO_read_get_new_id_address_from_session_uid(BlendLibReader *reader, uint session_uid)
ATTR_NONNULL(1);
/* Misc. */
bool BLO_read_lib_is_undo(BlendLibReader *reader);
Main *BLO_read_lib_get_main(BlendLibReader *reader);
BlendFileReadReport *BLO_read_lib_reports(BlendLibReader *reader);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Report API
* \{ */
/**
* This function ensures that reports are printed,
* in the case of library linking errors this is important!
*
* NOTE(@ideasman42) a kludge but better than doubling up on prints,
* we could alternatively have a versions of a report function which forces printing.
*/
void BLO_reportf_wrap(BlendFileReadReport *reports, eReportType type, const char *format, ...)
ATTR_PRINTF_FORMAT(3, 4);
/** \} */