Наследование типов¶
Принцип¶
Каждый тип хранит только собственные поля. Родительские поля не дублируются — при запросе система рекурсивно обходит цепочку предков.
Component [hp, speed]
└── MeshRenderer [mesh, material]
all_fields("MeshRenderer") → [hp, speed, mesh, material]
fields("MeshRenderer") → [mesh, material]
Хранение¶
InspectRegistry содержит два словаря:
| Словарь | Ключ | Значение |
|---|---|---|
_fields |
type_name |
Только собственные поля типа |
_type_parents |
type_name |
Имя родительского типа (строка) |
Регистрация родителя¶
C++¶
Макро REGISTER_COMPONENT автоматически регистрирует родителя:
// component_registry.hpp
REGISTER_COMPONENT(MeshRenderer, Component);
// Внутри раскрывается в:
InspectRegistry::instance().set_type_parent("MeshRenderer", "Component");
Ручная регистрация:
auto& reg = InspectRegistry::instance();
reg.add<Base, int>("Base", &Base::hp, "hp", "HP", "int");
reg.add<Derived, std::string>("Derived", &Derived::title, "title", "Title", "string");
reg.set_type_parent("Derived", "Base");
Python¶
__init_subclass__ обходит MRO и находит ближайшего предка с inspect_fields:
class Enemy(PythonComponent):
inspect_fields = {"hp": ("int", "HP")}
class Boss(Enemy):
inspect_fields = {"phase": ("int", "Phase")}
При определении Boss система:
- Обходит
Boss.__mro__→ находитEnemyкак ближайший предок сinspect_fields - Вызывает
registry.set_type_parent("Boss", "Enemy") - Регистрирует только собственные поля
Boss(безhp)
Разрешение полей¶
Все методы запроса полей работают рекурсивно:
all_fields(type_name)¶
Собирает полный список: сначала поля родителя (рекурсивно), потом собственные.
std::vector<InspectFieldInfo> all_fields(const std::string& type_name) const {
std::vector<InspectFieldInfo> result;
std::string parent = get_type_parent(type_name);
if (!parent.empty()) {
auto parent_fields = all_fields(parent);
result.insert(result.end(), parent_fields.begin(), parent_fields.end());
}
auto it = _fields.find(type_name);
if (it != _fields.end()) {
result.insert(result.end(), it->second.begin(), it->second.end());
}
return result;
}
Порядок полей: родительские первыми, затем собственные (как в C++ memory layout).
find_field(type_name, path)¶
Сначала ищет в собственных полях, потом рекурсивно в родителе:
const InspectFieldInfo* find_field(const std::string& type_name,
const std::string& path) const {
// Сначала свои
auto it = _fields.find(type_name);
if (it != _fields.end()) {
for (const auto& f : it->second) {
if (f.path == path) return &f;
}
}
// Потом родитель
std::string parent = get_type_parent(type_name);
if (!parent.empty()) {
return find_field(parent, path);
}
return nullptr;
}
get_field_by_index(type_name, index)¶
Учитывает смещение: если индекс попадает в диапазон родителя — делегирует туда, иначе вычитает parent_count и ищет в собственных.
C API¶
Диспетчер предоставляет единую функцию:
const char* tc_inspect_get_base_type(const char* type_name);
Возвращает имя родителя или NULL. Внутри вызывает get_parent из языкового vtable.
Важно: tc_inspect_field_count() и tc_inspect_get_field() уже возвращают данные с учётом наследования — C++ vtable делегирует в all_fields_count() и get_field_by_index().
Сериализация¶
all_fields() используется при сериализации/десериализации — все унаследованные поля обрабатываются автоматически:
// tc_inspect_python.cpp — десериализация с наследованием
for (const auto& f : reg.all_fields(type_name)) {
if (!f.is_serializable) continue;
if (!f.setter) continue;
// ... восстановление поля из данных
}
Полный пример¶
struct CppBaseComponent {
int hp = 100;
float speed = 2.5f;
};
struct CppDerivedComponent : public CppBaseComponent {
std::string title = "rookie";
};
auto& reg = InspectRegistry::instance();
// Регистрация полей — каждый тип только свои
reg.add<CppBaseComponent, int>(
"CppBaseComponent", &CppBaseComponent::hp, "hp", "HP", "int");
reg.add<CppBaseComponent, float>(
"CppBaseComponent", &CppBaseComponent::speed, "speed", "Speed", "float");
reg.add<CppDerivedComponent, std::string>(
"CppDerivedComponent", &CppDerivedComponent::title, "title", "Title", "string");
// Связь наследования
reg.set_type_parent("CppDerivedComponent", "CppBaseComponent");
// Результат
reg.all_fields_count("CppDerivedComponent"); // 3 (hp, speed, title)
reg.find_field("CppDerivedComponent", "hp"); // найдено — от родителя
reg.find_field("CppDerivedComponent", "title"); // найдено — собственное
reg.fields("CppDerivedComponent").size(); // 1 (только title)