ID Datablocks¶
Objects, meshes, materials, scenes, those are all examples of ID
Datablocks. They are generally defined in the DNA_{some type}_types.h
header files.
ID Datablocks are blocks of memory that start with a set of common
properties. In C this is modeled as a struct that embeds a struct of
type ID
as the first field. If it has animation data in the form of
an AnimData
struct, this MUST be the second field.
ID Data¶
Here is a brief overview of some key information included in the ID
structure. Its full definition can be found in the
DNA_ID.h
file.
Name & Type (ID.name
)
The name of the ID (pointer-\>id.name
) encodes the type (ID_Type
enum) of the datablock in its first two chars. For example, a mesh that
is presented in the Blender user interface with the name "Suzanne" is
actually named "MESuzanne"
in the ID.name
. This can be used to
cast an ID *id
to the correct ID_Type
. A macro GS(name)
is
available to take such a name and return a constant that indicates the
ID data type: if (GS(id-\>name) == ID_SC) …
can be used to test
whether the ID is a Scene.
ID Properties (ID.properties
)
IDs can store random user-defined data using the ID property system.
Tags (ID.tag
)
Tags are runtime-only information about the data-block status: does it belong the the Main data-base or not, is it evaluated data handled by the Depsgraph, is it local or linked data, etc.
Tags get reset on file read/write.
Flags (ID.flag
)
Flags are persistent information about the data-block status: does it have a 'fake user' that prevents it to be deleted automatically, is it an embedded data-block, etc.
Recalc Flags (ID.recalc
)
Flags used to control the Depsgraph Update process.
Users Refcounting (ID.us
)
A counter of how many other data-blocks are using this one.
Currently in Blender, if an ID has no (0
) users, it will not be
saved on disk. To preserve an unused ID, the LIB_FAKEUSER
flag must
be set, which enforce a non-zero user count.
ID and Relationships Between Data¶
Ideally, an ID should own and be responsible for all sub-data it uses, and not share any kind of ownership over it with any other ID. This includes handling of its allocation, freeing, copying, etc.
Therefore, a given ID should only reference other IDs (by pointers). Referencing sub-data of another ID should be avoided as much as possible, and never done by actual pointers (conceptually, the reference should then be composed of a pointer to the ID owning that sub-data, and some form of 'path' from this owner (e.g. the name of a modifier, bone, etc.)).
Not following those principles will severely increase complexity in ID management code, and make it much less robust and maintainble than it should be.
Bad Examples
Current code in Blender features several counter-examples to that ideal full separation of data between each ID. Those include:
- Poses (which are Object ID sub-data) directly reference their bone by pointer (bones are Armature ID sub-data). This creates a lot of issues and make handling of Armature data changes significantly more complicated than it should be.
- VertexGroups are somewhat 'split' between the Object ID (where their existence and names are defined) and the geometry ID (meshes, lattices etc., where their actual data [weight value for each point] is defined). This kind of data layout does not only add a very huge amount of complexity to the code, it is not only an infinite source of bugs, but it also has some intrinsic limitations that make it close to unusable when several objects share the same geometry
Runtime Registration of ID Types - IDTypeInfo¶
Before early 2020, ID types were hard-coded in Blender, and their
management code was spread all around BKE code base, often inside of
giant switch
statements covering all ID types.
This is being transformed into a runtime data structure (see T73719).
Ultimately this should allow to encapsulate all ID management tasks specific to an ID type into this structure, which can then be used in a fully generic way by the core ID management code.
Runtime type information is stored in a IDTypeInfo
struct. This
contains metadata of the struct, as well as pointers to functions for
generic operations (create, copy, delete, make local, file read/write,
looping over other ID usages, etc.). This is an example of such a
struct, in this case for Objects:
IDTypeInfo IDType_ID_OB = {
/* id_code */ ID_OB,
/* id_filter */ FILTER_ID_OB,
/* main_listbase_index */ INDEX_ID_OB,
/* struct_size */ sizeof(Object),
/* name */ "Object",
/* name_plural */ "objects",
/* translation_context */ BLT_I18NCONTEXT_ID_OBJECT,
/* flags */ 0,
/* asset_type_info */ &AssetType_OB,
/* init_data */ object_init_data,
/* copy_data */ object_copy_data,
/* free_data */ object_free_data,
/* make_local */ object_make_local,
/* foreach_id */ object_foreach_id,
/* foreach_cache */ nullptr,
/* foreach_path */ object_foreach_path,
/* owner_get */ nullptr,
/* blend_write */ object_blend_write,
/* blend_read_data */ object_blend_read_data,
/* blend_read_lib */ object_blend_read_lib,
/* blend_read_expand */ object_blend_read_expand,
/* blend_read_undo_preserve */ nullptr,
/* lib_override_apply_post */ object_lib_override_apply_post,
};
BKE_idtype.hh defines the IDTypeInfo structure, its callbacks, and an API to help with handling of ID types.
Adding a new ID Type¶
To add a new IDTypeInfo
you need to:
- Define the
IDTypeInfo
in the relevant BKE's implementation file (e.g.mesh.c
forID_ME
, etc.). - Put thestatic
callbacks and then the IDTypeInfo struct definition at the start of those files. - If you need extra helpers, put only their forward declaration there, and implement them below theIDTypeInfo
definition. This allows for a consistent clear separation between common info and specific APIs for each ID type implementation file. - The IDTypeInfo should be named from its idcode (e.g. IDType_ID_OB for object). - Add the
extern
declaration of the newIDType
inBKE_idtype.h
. - Register the new
IDTypeInfo
inid_type_init()
ofidtype.cc
.