Core: RNA: Use VectorSet for StructRNA properties lookup

Replace the  `Ghash` `prophash` properties map in RNA containers by a
`CustomIDVectorSet` `prop_lookup_set`.

This commit also allows for runtime-defined RNA Struct types to use
`CustomIDVectorSet` for properties lookup, instead of only relying on linear
search in the ListBase property list.

NOTE: This also simplifies a bit the code compared to using Ghash, since the
string key is contained whiting the property, and not stored separately as in the
GHash. It also means that the string key (the property identifier)  is 'automatically'
updated in the VectorSet when updated in the property itself. This avoids the
need to re-insert the property when its identifier string is duplicated into a new
memory address. However, modifying the property identifier would cause
undefined behavior if the property is not removed from the vector set first.

-----

While its an impractical example, the following custom PropertyGroup example shows the potential slowdowns just
of looking through the ListBase

``` py
import bpy

test_properties_script = "import bpy\nclass TestProperties(bpy.types.PropertyGroup):"

property_count=10000
for x in range(property_count):
    test_properties_script+="\n    prop_{0} : bpy.props.IntProperty(name=\"Prop {0}\")".format(x)

exec(test_properties_script)

class LayoutDemoPanel(bpy.types.Panel):
    """Creates a Test in the text editor side panel to test ui perfomance"""
    bl_label = "Test"
    bl_category = "Test"
    bl_idname = "TEXT_PT_test"
    bl_space_type = 'TEXT_EDITOR'
    bl_region_type = 'UI'

    def draw(self, context):
        layout = self.layout
        layout.use_property_split=True
        layout.use_property_decorate=False
        for x in range(property_count):
            row = layout.row()
            row.prop(bpy.data.scenes['Scene'].test_pointer, "prop_{}".format(x))

classes = [
    TestProperties,
    LayoutDemoPanel,
]

if hasattr(bpy.types.Scene,'test_pointer'):
    del bpy.types.Scene.test_pointer

class_register, class_unregister = bpy.utils.register_classes_factory(classes)

class_register()

bpy.types.Scene.test_pointer = bpy.props.PointerProperty(type=TestProperties)
```

<video src="attachments/95e36a02-b781-44b1-9a18-9e453f8a1432" title="2025-02-03 14-12-47.mp4" controls></video>

Pull Request: https://projects.blender.org/blender/blender/pulls/134000
This commit is contained in:
Guillermo Venegas
2025-03-05 19:34:19 +01:00
committed by Bastien Montagne
parent 97055868a3
commit 82e2a57dcd
4 changed files with 35 additions and 28 deletions

View File

@@ -87,12 +87,14 @@ void RNA_init()
for (srna = static_cast<StructRNA *>(BLENDER_RNA.structs.first); srna;
srna = static_cast<StructRNA *>(srna->cont.next))
{
if (!srna->cont.prophash) {
srna->cont.prophash = BLI_ghash_str_new("RNA_init gh");
if (!srna->cont.prop_lookup_set) {
srna->cont.prop_lookup_set =
MEM_new<blender::CustomIDVectorSet<PropertyRNA *, PropertyRNAIdentifierGetter>>(
__func__);
LISTBASE_FOREACH (PropertyRNA *, prop, &srna->cont.properties) {
if (!(prop->flag_internal & PROP_INTERN_BUILTIN)) {
BLI_ghash_insert(srna->cont.prophash, (void *)prop->identifier, prop);
srna->cont.prop_lookup_set->add(prop);
}
}
}
@@ -124,10 +126,7 @@ void RNA_exit()
for (srna = static_cast<StructRNA *>(BLENDER_RNA.structs.first); srna;
srna = static_cast<StructRNA *>(srna->cont.next))
{
if (srna->cont.prophash) {
BLI_ghash_free(srna->cont.prophash, nullptr, nullptr);
srna->cont.prophash = nullptr;
}
MEM_SAFE_DELETE(srna->cont.prop_lookup_set);
}
RNA_free(&BLENDER_RNA);

View File

@@ -814,7 +814,7 @@ void RNA_struct_free(BlenderRNA *brna, StructRNA *srna)
srna_identifier);
}
}
MEM_SAFE_DELETE(srna->cont.prop_lookup_set);
for (prop = static_cast<PropertyRNA *>(srna->cont.properties.first); prop; prop = nextprop) {
nextprop = prop->next;
@@ -951,7 +951,7 @@ StructRNA *RNA_def_struct_ptr(BlenderRNA *brna, const char *identifier, StructRN
/* Copy from struct to derive stuff, a bit clumsy since we can't
* use #MEM_dupallocN, data structs may not be allocated but builtin. */
memcpy(srna, srnafrom, sizeof(StructRNA));
srna->cont.prophash = nullptr;
srna->cont.prop_lookup_set = nullptr;
BLI_listbase_clear(&srna->cont.properties);
BLI_listbase_clear(&srna->functions);
srna->py_type = nullptr;
@@ -1003,6 +1003,8 @@ StructRNA *RNA_def_struct_ptr(BlenderRNA *brna, const char *identifier, StructRN
}
else {
RNA_def_struct_flag(srna, STRUCT_RUNTIME);
srna->cont.prop_lookup_set =
MEM_new<blender::CustomIDVectorSet<PropertyRNA *, PropertyRNAIdentifierGetter>>(__func__);
}
if (srnafrom) {
@@ -1013,6 +1015,10 @@ StructRNA *RNA_def_struct_ptr(BlenderRNA *brna, const char *identifier, StructRN
/* define some builtin properties */
prop = RNA_def_property(&srna->cont, "rna_properties", PROP_COLLECTION, PROP_NONE);
prop->flag_internal |= PROP_INTERN_BUILTIN;
/* Properties with internal flag #PROP_INTERN_BUILTIN are not included for lookup. */
if (srna->cont.prop_lookup_set) {
srna->cont.prop_lookup_set->remove_as(prop->identifier);
}
RNA_def_property_ui_text(prop, "Properties", "RNA property collection");
if (DefRNA.preprocess) {
@@ -1490,8 +1496,8 @@ PropertyRNA *RNA_def_property(StructOrFunctionRNA *cont_,
RNA_def_property_flag(prop, PROP_IDPROPERTY);
prop->flag_internal |= PROP_INTERN_RUNTIME;
#ifdef RNA_RUNTIME
if (cont->prophash) {
BLI_ghash_insert(cont->prophash, (void *)prop->identifier, prop);
if (cont->prop_lookup_set) {
cont->prop_lookup_set->add(prop);
}
#endif
}
@@ -4863,21 +4869,12 @@ void RNA_def_func_free_pointers(FunctionRNA *func)
}
}
void RNA_def_property_duplicate_pointers(StructOrFunctionRNA *cont_, PropertyRNA *prop)
void RNA_def_property_duplicate_pointers(StructOrFunctionRNA * /*cont_*/, PropertyRNA *prop)
{
ContainerRNA *cont = static_cast<ContainerRNA *>(cont_);
int a;
/* annoying since we just added this to a hash, could make this add the correct key to the hash
* in the first place */
if (prop->identifier) {
if (cont->prophash) {
prop->identifier = BLI_strdup(prop->identifier);
BLI_ghash_reinsert(cont->prophash, (void *)prop->identifier, prop, nullptr, nullptr);
}
else {
prop->identifier = BLI_strdup(prop->identifier);
}
prop->identifier = BLI_strdup(prop->identifier);
}
if (prop->name) {
@@ -5053,8 +5050,8 @@ static void rna_def_property_free(StructOrFunctionRNA *cont_, PropertyRNA *prop)
ContainerRNA *cont = static_cast<ContainerRNA *>(cont_);
if (prop->flag_internal & PROP_INTERN_RUNTIME) {
if (cont->prophash) {
BLI_ghash_remove(cont->prophash, prop->identifier, nullptr, nullptr);
if (cont->prop_lookup_set) {
cont->prop_lookup_set->remove_as(prop->identifier);
}
RNA_def_property_free_pointers(prop);

View File

@@ -11,6 +11,8 @@
#include <optional>
#include <string>
#include "BLI_vector_set.hh"
#include "DNA_listBase.h"
#include "RNA_access.hh"
@@ -287,11 +289,15 @@ struct RNAPropertyOverrideApplyContext {
};
using RNAPropOverrideApply = bool (*)(Main *bmain, RNAPropertyOverrideApplyContext &rnaapply_ctx);
struct PropertyRNAIdentifierGetter {
blender::StringRef operator()(const PropertyRNA *prop) const;
};
/* Container - generic abstracted container of RNA properties */
struct ContainerRNA {
void *next, *prev;
struct GHash *prophash;
blender::CustomIDVectorSet<PropertyRNA *, PropertyRNAIdentifierGetter> *prop_lookup_set;
ListBase properties;
};
@@ -385,6 +391,11 @@ struct PropertyRNA {
void *py_data;
};
inline blender::StringRef PropertyRNAIdentifierGetter::operator()(const PropertyRNA *prop) const
{
return prop->identifier;
}
/* internal flags WARNING! 16bits only! */
enum PropertyFlagIntern {
PROP_INTERN_BUILTIN = (1 << 0),

View File

@@ -563,9 +563,9 @@ bool rna_builtin_properties_lookup_string(PointerRNA *ptr, const char *key, Poin
srna = ptr->type;
do {
if (srna->cont.prophash) {
prop = static_cast<PropertyRNA *>(BLI_ghash_lookup(srna->cont.prophash, (void *)key));
if (srna->cont.prop_lookup_set) {
PropertyRNA *const *lookup_prop = srna->cont.prop_lookup_set->lookup_key_ptr_as(key);
prop = lookup_prop ? *lookup_prop : nullptr;
if (prop) {
*r_ptr = {nullptr, &RNA_Property, prop};
return true;