Core: add functions to print DNA structs with all their members
This adds a new `DNA_print.hh` header which contains functions to print DNA structs with all their data members in a human readable form. This is intended for debugging purposes. The basic usage is very straight forward: `DNA_print_struct(TypeName, data);`. For example: `DNA_print_struct(bNode, node);`. There is also `DNA_print_structs_at_address` which is primarily useful when debugging what is written to a .blend file. This was originally developed for #133063, but is already quite useful on its own. Pull Request: https://projects.blender.org/blender/blender/pulls/133432
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
|
||||
/* For #SDNA_TYPE_FROM_STRUCT macro. */
|
||||
#include "dna_type_offsets.h"
|
||||
|
||||
struct SDNA;
|
||||
struct SDNA_Struct;
|
||||
|
||||
namespace blender::dna {
|
||||
|
||||
/**
|
||||
* Print all members of the struct assuming that the data has the given address. This is mainly
|
||||
* useful for observing what data is written to a .blend file.
|
||||
*
|
||||
* \param sdna: Contains reflection information about DNA structs.
|
||||
* \param struct_id: The type the data points to. Used to index into `sdna.structs`.
|
||||
* \param data: Where the data is stored.
|
||||
* \param address: The address that should be printed. Often it's the same as `data`.
|
||||
* \param element_num: The number of elements in the array, or 1 if there is only one struct.
|
||||
* \param stream: Where to print the output.
|
||||
*/
|
||||
void print_structs_at_address(const SDNA &sdna,
|
||||
int struct_id,
|
||||
const void *data,
|
||||
const void *address,
|
||||
int64_t element_num,
|
||||
std::ostream &stream);
|
||||
|
||||
/**
|
||||
* Prints all members of the struct to stdout.
|
||||
*/
|
||||
void print_struct_by_id(int struct_id, const void *data);
|
||||
|
||||
} // namespace blender::dna
|
||||
|
||||
/**
|
||||
* Prints all members of the struct to stdout.
|
||||
*
|
||||
* Usage:
|
||||
* DNA_print_struct(bNode, node);
|
||||
*/
|
||||
#define DNA_print_struct(struct_name, data_ptr) \
|
||||
blender::dna::print_struct_by_id(SDNA_TYPE_FROM_STRUCT(struct_name), data_ptr)
|
||||
@@ -18,6 +18,7 @@ set(LIB
|
||||
PRIVATE bf::intern::atomic
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::dependencies::optional::tbb
|
||||
PRIVATE bf::extern::fmtlib
|
||||
)
|
||||
|
||||
add_definitions(-DWITH_DNA_GHASH)
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "MEM_guardedalloc.h" /* for MEM_freeN MEM_mallocN MEM_callocN */
|
||||
|
||||
#include "BLI_endian_switch.h"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_utildefines.h"
|
||||
@@ -28,6 +32,7 @@
|
||||
#include "BLI_ghash.h"
|
||||
|
||||
#include "DNA_genfile.h"
|
||||
#include "DNA_print.hh"
|
||||
#include "DNA_sdna_types.h" /* for SDNA ;-) */
|
||||
|
||||
/**
|
||||
@@ -2050,3 +2055,210 @@ void DNA_sdna_alias_data_ensure_structs_map(SDNA *sdna)
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Print DNA structs
|
||||
*
|
||||
* \{ */
|
||||
|
||||
namespace blender::dna {
|
||||
|
||||
static void print_struct_array_recursive(const SDNA &sdna,
|
||||
const SDNA_Struct &sdna_struct,
|
||||
const void *initial_data,
|
||||
const int64_t element_num,
|
||||
const int indent,
|
||||
fmt::appender &dst);
|
||||
static void print_single_struct_recursive(const SDNA &sdna,
|
||||
const SDNA_Struct &sdna_struct,
|
||||
const void *initial_data,
|
||||
const int indent,
|
||||
fmt::appender &dst);
|
||||
|
||||
/**
|
||||
* Uses a heuristic to detect if a char array should be printed as string.
|
||||
*/
|
||||
static bool char_array_startswith_simple_name(const char *data, const int array_len)
|
||||
{
|
||||
const int string_length = strnlen(data, array_len);
|
||||
if (string_length == array_len) {
|
||||
return false;
|
||||
}
|
||||
for (const int i : IndexRange(string_length)) {
|
||||
const unsigned char c = data[i];
|
||||
/* This is only a very simple check and does not cover more complex cases with multi-byte UTF-8
|
||||
* characters. It's only a heuristic anyway, making a wrong decision here just means that the
|
||||
* data will be printed differently. */
|
||||
if (!std::isprint(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void print_struct_array_recursive(const SDNA &sdna,
|
||||
const SDNA_Struct &sdna_struct,
|
||||
const void *data,
|
||||
const int64_t element_num,
|
||||
const int indent,
|
||||
fmt::appender &dst)
|
||||
{
|
||||
if (element_num == 1) {
|
||||
print_single_struct_recursive(sdna, sdna_struct, data, indent, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *struct_name = sdna.types[sdna_struct.type_index];
|
||||
const int64_t struct_size = sdna.types_size[sdna_struct.type_index];
|
||||
for (const int64_t i : IndexRange(element_num)) {
|
||||
const void *element_data = POINTER_OFFSET(data, i * struct_size);
|
||||
fmt::format_to(dst, "{:{}}{}: <{}>\n", "", indent, i, struct_name);
|
||||
print_single_struct_recursive(sdna, sdna_struct, element_data, indent + 2, dst);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_single_struct_recursive(const SDNA &sdna,
|
||||
const SDNA_Struct &sdna_struct,
|
||||
const void *initial_data,
|
||||
const int indent,
|
||||
fmt::appender &dst)
|
||||
{
|
||||
using namespace blender;
|
||||
const void *data = initial_data;
|
||||
|
||||
for (const int member_i : IndexRange(sdna_struct.members_num)) {
|
||||
const SDNA_StructMember &member = sdna_struct.members[member_i];
|
||||
const char *member_type_name = sdna.types[member.type_index];
|
||||
const char *member_name = sdna.members[member.member_index];
|
||||
const eStructMemberCategory member_category = get_struct_member_category(&sdna, &member);
|
||||
const int member_array_len = sdna.members_array_num[member.member_index];
|
||||
|
||||
fmt::format_to(dst, "{:{}}{} {}:", "", indent, member_type_name, member_name);
|
||||
|
||||
if (member_category == STRUCT_MEMBER_CATEGORY_PRIMITIVE &&
|
||||
member.type_index == SDNA_TYPE_CHAR && member_array_len > 1)
|
||||
{
|
||||
const char *str_data = static_cast<const char *>(data);
|
||||
fmt::format_to(dst, " ");
|
||||
if (char_array_startswith_simple_name(str_data, member_array_len)) {
|
||||
fmt::format_to(dst, "'{}'", str_data);
|
||||
}
|
||||
else {
|
||||
for (const int i : IndexRange(member_array_len)) {
|
||||
fmt::format_to(dst, "{} ", int(str_data[i]));
|
||||
}
|
||||
}
|
||||
fmt::format_to(dst, "\n");
|
||||
}
|
||||
else {
|
||||
switch (member_category) {
|
||||
case STRUCT_MEMBER_CATEGORY_STRUCT: {
|
||||
fmt::format_to(dst, "\n");
|
||||
const int substruct_i = DNA_struct_find_index_without_alias(&sdna, member_type_name);
|
||||
const SDNA_Struct &sub_sdna_struct = *sdna.structs[substruct_i];
|
||||
print_struct_array_recursive(
|
||||
sdna, sub_sdna_struct, data, member_array_len, indent + 2, dst);
|
||||
break;
|
||||
}
|
||||
case STRUCT_MEMBER_CATEGORY_PRIMITIVE: {
|
||||
fmt::format_to(dst, " ");
|
||||
const int type_size = sdna.types_size[member.type_index];
|
||||
const eSDNA_Type type = eSDNA_Type(member.type_index);
|
||||
for ([[maybe_unused]] const int elem_i : IndexRange(member_array_len)) {
|
||||
const void *current_data = POINTER_OFFSET(data, elem_i * type_size);
|
||||
switch (type) {
|
||||
case SDNA_TYPE_CHAR: {
|
||||
const char value = *reinterpret_cast<const char *>(current_data);
|
||||
fmt::format_to(dst, "{}", int(value));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_UCHAR: {
|
||||
const uchar value = *reinterpret_cast<const uchar *>(current_data);
|
||||
fmt::format_to(dst, "{}", int(value));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_INT8: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const int8_t *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_SHORT: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const short *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_USHORT: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const ushort *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_INT: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const int *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_FLOAT: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const float *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_DOUBLE: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const double *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_INT64: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const int64_t *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_UINT64: {
|
||||
fmt::format_to(dst, "{}", *reinterpret_cast<const uint64_t *>(current_data));
|
||||
break;
|
||||
}
|
||||
case SDNA_TYPE_RAW_DATA: {
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
fmt::format_to(dst, " ");
|
||||
}
|
||||
fmt::format_to(dst, "\n");
|
||||
break;
|
||||
}
|
||||
case STRUCT_MEMBER_CATEGORY_POINTER: {
|
||||
for ([[maybe_unused]] const int elem_i : IndexRange(member_array_len)) {
|
||||
const void *current_data = POINTER_OFFSET(data, sdna.pointer_size * elem_i);
|
||||
fmt::format_to(dst, " {}", *reinterpret_cast<const void *const *>(current_data));
|
||||
}
|
||||
fmt::format_to(dst, "\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const int member_size = get_member_size_in_bytes(&sdna, &member);
|
||||
data = POINTER_OFFSET(data, member_size);
|
||||
}
|
||||
}
|
||||
|
||||
void print_structs_at_address(const SDNA &sdna,
|
||||
const int struct_id,
|
||||
const void *initial_data,
|
||||
const void *address,
|
||||
const int64_t element_num,
|
||||
std::ostream &stream)
|
||||
{
|
||||
const SDNA_Struct &sdna_struct = *sdna.structs[struct_id];
|
||||
|
||||
fmt::memory_buffer buf;
|
||||
fmt::appender dst{buf};
|
||||
|
||||
const char *struct_name = sdna.types[sdna_struct.type_index];
|
||||
fmt::format_to(dst, "<{}> {}x at {}\n", struct_name, element_num, address);
|
||||
|
||||
print_struct_array_recursive(sdna, sdna_struct, initial_data, element_num, 2, dst);
|
||||
stream << fmt::to_string(buf);
|
||||
}
|
||||
|
||||
void print_struct_by_id(const int struct_id, const void *data)
|
||||
{
|
||||
const SDNA &sdna = *DNA_sdna_current_get();
|
||||
print_structs_at_address(sdna, struct_id, data, data, 1, std::cout);
|
||||
}
|
||||
|
||||
} // namespace blender::dna
|
||||
|
||||
/** \} */
|
||||
|
||||
Reference in New Issue
Block a user