diff --git a/pam_pmm.h b/pam_pmm.h index 164c9ea..a1d4252 100644 --- a/pam_pmm.h +++ b/pam_pmm.h @@ -165,12 +165,11 @@ static_assert( std::is_trivially_copyable::value, * @brief Инкапсулированное состояние PMM-фасада. * * Три ранее разрозненные статические переменные (filename, root_offset, - * initialized) объединены в одну структуру. Это первый шаг к поддержке - * нескольких экземпляров БД в одном процессе (Проблема 3, plan.md). + * initialized) объединены в одну структуру (Этап A, Issue #205). * - * Текущая реализация использует глобальный синглтон (pam_pmm_global_state()), - * а функции detail:: делегируют ему. В дальнейшем (Этап 10.1b) состояние - * будет передаваться как явный параметр. + * Этап B (Issue #209): все pam_pmm_* функции принимают pam_pmm_state& + * как явный параметр. Глобальные обёртки без параметра сохранены для + * обратной совместимости и делегируют глобальному синглтону. */ struct pam_pmm_state { @@ -222,27 +221,39 @@ inline bool& pam_pmm_initialized() // ═══════════════════════════════════════════════════════════════════════════ /** - * @brief Получить указатель на корневую структуру. + * @brief Получить указатель на корневую структуру (явное состояние). */ -inline pam_pmm_root* pam_pmm_get_root() +inline pam_pmm_root* pam_pmm_get_root( pam_pmm_state& state ) { - uintptr_t off = detail::pam_pmm_root_offset(); + uintptr_t off = state.root_offset; if ( off == 0 ) return nullptr; return pmm_resolve( off ); } +/// Обёртка для обратной совместимости (делегирует глобальному состоянию). +inline pam_pmm_root* pam_pmm_get_root() +{ + return pam_pmm_get_root( pam_pmm_global_state() ); +} + /** - * @brief Получить указатель на реестр. + * @brief Получить указатель на реестр (явное состояние). */ -inline pam_pmm_registry* pam_pmm_get_registry() +inline pam_pmm_registry* pam_pmm_get_registry( pam_pmm_state& state ) { - pam_pmm_root* root = pam_pmm_get_root(); + pam_pmm_root* root = pam_pmm_get_root( state ); if ( root == nullptr || root->registry_off == 0 ) return nullptr; return pmm_resolve( root->registry_off ); } +/// Обёртка для обратной совместимости. +inline pam_pmm_registry* pam_pmm_get_registry() +{ + return pam_pmm_get_registry( pam_pmm_global_state() ); +} + /** * @brief Создать новый реестр в ПАП. * @@ -312,21 +323,22 @@ inline uintptr_t pam_pmm_create_root_and_registry() } /** - * @brief Инициализировать PMM из файла или создать новое хранилище. + * @brief Инициализировать PMM из файла или создать новое хранилище (явное состояние). * + * @param state Состояние PMM-фасада. * @param filename Путь к файлу хранилища (может быть nullptr для in-memory). */ -inline void pam_pmm_init( const char* filename ) +inline void pam_pmm_init( pam_pmm_state& state, const char* filename ) { // Сохраняем имя файла. if ( filename != nullptr ) { - std::strncpy( detail::pam_pmm_filename(), filename, 255 ); - detail::pam_pmm_filename()[255] = '\0'; + std::strncpy( state.filename, filename, 255 ); + state.filename[255] = '\0'; } else { - detail::pam_pmm_filename()[0] = '\0'; + state.filename[0] = '\0'; } // Пытаемся загрузить существующий файл. @@ -361,8 +373,8 @@ inline void pam_pmm_init( const char* filename ) pam_pmm_root* root = root_pptr.resolve(); if ( root != nullptr && root->magic == PAM_PMM_MAGIC && root->version == PAM_PMM_VERSION ) { - detail::pam_pmm_root_offset() = root_pptr.byte_offset(); - loaded = true; + state.root_offset = root_pptr.byte_offset(); + loaded = true; } } } @@ -382,65 +394,89 @@ inline void pam_pmm_init( const char* filename ) PamManager::create( PAM_PMM_INITIAL_SIZE ); // Создаём корневую структуру и реестр. - uintptr_t root_off = pam_pmm_create_root_and_registry(); - detail::pam_pmm_root_offset() = root_off; + uintptr_t root_off = pam_pmm_create_root_and_registry(); + state.root_offset = root_off; } - detail::pam_pmm_initialized() = true; + state.initialized = true; +} + +/// Обёртка для обратной совместимости. +inline void pam_pmm_init( const char* filename ) +{ + pam_pmm_init( pam_pmm_global_state(), filename ); } /** - * @brief Сохранить PMM в файл. - * - * Сохраняет PMM данные на диск. + * @brief Сохранить PMM в файл (явное состояние). */ -inline void pam_pmm_save() +inline void pam_pmm_save( pam_pmm_state& state ) { - const char* filename = detail::pam_pmm_filename(); - if ( filename[0] == '\0' ) + if ( state.filename[0] == '\0' ) return; - pmm::save_manager( filename ); + pmm::save_manager( state.filename ); +} + +/// Обёртка для обратной совместимости. +inline void pam_pmm_save() +{ + pam_pmm_save( pam_pmm_global_state() ); } /** - * @brief Уничтожить PMM и освободить ресурсы. + * @brief Уничтожить PMM и освободить ресурсы (явное состояние). * * Сохраняет данные перед уничтожением, если указан файл. */ -inline void pam_pmm_destroy() +inline void pam_pmm_destroy( pam_pmm_state& state ) { - pam_pmm_save(); + pam_pmm_save( state ); PamManager::destroy(); + state.reset(); +} - detail::pam_pmm_filename()[0] = '\0'; - detail::pam_pmm_root_offset() = 0; - detail::pam_pmm_initialized() = false; +/// Обёртка для обратной совместимости. +inline void pam_pmm_destroy() +{ + pam_pmm_destroy( pam_pmm_global_state() ); } /** - * @brief Сбросить PMM к пустому состоянию за O(1). + * @brief Сбросить PMM к пустому состоянию за O(1) (явное состояние). * * Пересоздаёт хранилище с чистым состоянием. */ -inline void pam_pmm_reset() +inline void pam_pmm_reset( pam_pmm_state& state ) { // Уничтожаем и создаём заново. PamManager::destroy(); PamManager::create( PAM_PMM_INITIAL_SIZE ); // Создаём корневую структуру и реестр. - uintptr_t root_off = pam_pmm_create_root_and_registry(); - detail::pam_pmm_root_offset() = root_off; - detail::pam_pmm_initialized() = true; + uintptr_t root_off = pam_pmm_create_root_and_registry(); + state.root_offset = root_off; + state.initialized = true; +} + +/// Обёртка для обратной совместимости. +inline void pam_pmm_reset() +{ + pam_pmm_reset( pam_pmm_global_state() ); } /** - * @brief Проверить, инициализирован ли PMM. + * @brief Проверить, инициализирован ли PMM (явное состояние). */ +inline bool pam_pmm_is_initialized( const pam_pmm_state& state ) +{ + return state.initialized && PamManager::is_initialized(); +} + +/// Обёртка для обратной совместимости. inline bool pam_pmm_is_initialized() { - return detail::pam_pmm_initialized() && PamManager::is_initialized(); + return pam_pmm_is_initialized( pam_pmm_global_state() ); } // ═══════════════════════════════════════════════════════════════════════════ @@ -448,18 +484,19 @@ inline bool pam_pmm_is_initialized() // ═══════════════════════════════════════════════════════════════════════════ /** - * @brief Создать один объект типа T в ПАП. + * @brief Создать один объект типа T в ПАП (явное состояние). * * @tparam T Тип создаваемого объекта. Должен быть тривиально копируемым. + * @param state Состояние PMM-фасада. * @param name Имя объекта (может быть nullptr для безымянного). * @return Байтовое смещение объекта в ПАП; 0 при ошибке. */ -template inline uintptr_t pam_pmm_create( const char* name = nullptr ) +template inline uintptr_t pam_pmm_create( pam_pmm_state& state, const char* name = nullptr ) { static_assert( std::is_trivially_copyable::value, "pam_pmm_create требует, чтобы T был тривиально копируемым" ); - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -486,7 +523,7 @@ template inline uintptr_t pam_pmm_create( const char* name = nullpt std::memset( static_cast( obj ), 0, sizeof( T ) ); // Перезапрашиваем указатель на реестр после аллокации. - reg = pam_pmm_get_registry(); + reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -502,7 +539,7 @@ template inline uintptr_t pam_pmm_create( const char* name = nullpt if ( name != nullptr && name[0] != '\0' ) { // Перезапрашиваем указатель после вставки в slot_map_. - reg = pam_pmm_get_registry(); + reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -514,15 +551,23 @@ template inline uintptr_t pam_pmm_create( const char* name = nullpt return obj_off; } +/// Обёртка для обратной совместимости. +template inline uintptr_t pam_pmm_create( const char* name = nullptr ) +{ + return pam_pmm_create( pam_pmm_global_state(), name ); +} + /** - * @brief Создать массив из count объектов типа T в ПАП. + * @brief Создать массив из count объектов типа T в ПАП (явное состояние). * * @tparam T Тип элементов массива. Должен быть тривиально копируемым. + * @param state Состояние PMM-фасада. * @param count Количество элементов. * @param name Имя массива (может быть nullptr). * @return Байтовое смещение первого элемента; 0 при ошибке. */ -template inline uintptr_t pam_pmm_create_array( unsigned count, const char* name = nullptr ) +template +inline uintptr_t pam_pmm_create_array( pam_pmm_state& state, unsigned count, const char* name = nullptr ) { static_assert( std::is_trivially_copyable::value, "pam_pmm_create_array требует, чтобы T был тривиально копируемым" ); @@ -530,7 +575,7 @@ template inline uintptr_t pam_pmm_create_array( unsigned count, con if ( count == 0 ) return 0; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -557,7 +602,7 @@ template inline uintptr_t pam_pmm_create_array( unsigned count, con std::memset( static_cast( arr ), 0, sizeof( T ) * count ); // Перезапрашиваем указатель на реестр после аллокации. - reg = pam_pmm_get_registry(); + reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -573,7 +618,7 @@ template inline uintptr_t pam_pmm_create_array( unsigned count, con if ( name != nullptr && name[0] != '\0' ) { // Перезапрашиваем указатель после вставки. - reg = pam_pmm_get_registry(); + reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -585,21 +630,28 @@ template inline uintptr_t pam_pmm_create_array( unsigned count, con return arr_off; } +/// Обёртка для обратной совместимости. +template inline uintptr_t pam_pmm_create_array( unsigned count, const char* name = nullptr ) +{ + return pam_pmm_create_array( pam_pmm_global_state(), count, name ); +} + // ═══════════════════════════════════════════════════════════════════════════ // УДАЛЕНИЕ ОБЪЕКТОВ // ═══════════════════════════════════════════════════════════════════════════ /** - * @brief Удалить объект по байтовому смещению. + * @brief Удалить объект по байтовому смещению (явное состояние). * + * @param state Состояние PMM-фасада. * @param offset Байтовое смещение объекта. */ -inline void pam_pmm_delete( uintptr_t offset ) +inline void pam_pmm_delete( pam_pmm_state& state, uintptr_t offset ) { if ( offset == 0 ) return; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return; @@ -624,7 +676,7 @@ inline void pam_pmm_delete( uintptr_t offset ) } // Перезапрашиваем реестр после удаления из name_map_. - reg = pam_pmm_get_registry(); + reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return; @@ -637,22 +689,29 @@ inline void pam_pmm_delete( uintptr_t offset ) PamManager::template deallocate_typed( p ); } +/// Обёртка для обратной совместимости. +inline void pam_pmm_delete( uintptr_t offset ) +{ + pam_pmm_delete( pam_pmm_global_state(), offset ); +} + // ═══════════════════════════════════════════════════════════════════════════ // ПОИСК ОБЪЕКТОВ // ═══════════════════════════════════════════════════════════════════════════ /** - * @brief Найти объект по имени. + * @brief Найти объект по имени (явное состояние). * + * @param state Состояние PMM-фасада. * @param name Имя объекта. * @return Байтовое смещение объекта; 0 если не найден. */ -inline uintptr_t pam_pmm_find( const char* name ) +inline uintptr_t pam_pmm_find( pam_pmm_state& state, const char* name ) { if ( name == nullptr || name[0] == '\0' ) return 0; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -666,19 +725,26 @@ inline uintptr_t pam_pmm_find( const char* name ) return slot->offset; } +/// Обёртка для обратной совместимости. +inline uintptr_t pam_pmm_find( const char* name ) +{ + return pam_pmm_find( pam_pmm_global_state(), name ); +} + /** - * @brief Найти объект по имени с проверкой размера элемента. + * @brief Найти объект по имени с проверкой размера элемента (явное состояние). * * @tparam T Ожидаемый тип объекта. + * @param state Состояние PMM-фасада. * @param name Имя объекта. * @return Байтовое смещение объекта; 0 если не найден или размер не совпадает. */ -template inline uintptr_t pam_pmm_find_typed( const char* name ) +template inline uintptr_t pam_pmm_find_typed( pam_pmm_state& state, const char* name ) { if ( name == nullptr || name[0] == '\0' ) return 0; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -696,18 +762,25 @@ template inline uintptr_t pam_pmm_find_typed( const char* name ) return slot->offset; } +/// Обёртка для обратной совместимости. +template inline uintptr_t pam_pmm_find_typed( const char* name ) +{ + return pam_pmm_find_typed( pam_pmm_global_state(), name ); +} + /** - * @brief Получить имя объекта по смещению. + * @brief Получить имя объекта по смещению (явное состояние). * + * @param state Состояние PMM-фасада. * @param offset Байтовое смещение объекта. * @return Указатель на строку имени или nullptr. */ -inline const char* pam_pmm_get_name( uintptr_t offset ) +inline const char* pam_pmm_get_name( pam_pmm_state& state, uintptr_t offset ) { if ( offset == 0 ) return nullptr; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return nullptr; @@ -721,18 +794,25 @@ inline const char* pam_pmm_get_name( uintptr_t offset ) return nullptr; } +/// Обёртка для обратной совместимости. +inline const char* pam_pmm_get_name( uintptr_t offset ) +{ + return pam_pmm_get_name( pam_pmm_global_state(), offset ); +} + /** - * @brief Получить количество элементов для слота. + * @brief Получить количество элементов для слота (явное состояние). * + * @param state Состояние PMM-фасада. * @param offset Байтовое смещение объекта. * @return Количество элементов; 0 если не найден. */ -inline uintptr_t pam_pmm_get_count( uintptr_t offset ) +inline uintptr_t pam_pmm_get_count( pam_pmm_state& state, uintptr_t offset ) { if ( offset == 0 ) return 0; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -743,18 +823,25 @@ inline uintptr_t pam_pmm_get_count( uintptr_t offset ) return slot->count; } +/// Обёртка для обратной совместимости. +inline uintptr_t pam_pmm_get_count( uintptr_t offset ) +{ + return pam_pmm_get_count( pam_pmm_global_state(), offset ); +} + /** - * @brief Получить размер элемента для слота. + * @brief Получить размер элемента для слота (явное состояние). * + * @param state Состояние PMM-фасада. * @param offset Байтовое смещение объекта. * @return Размер одного элемента в байтах; 0 если не найден. */ -inline uintptr_t pam_pmm_get_elem_size( uintptr_t offset ) +inline uintptr_t pam_pmm_get_elem_size( pam_pmm_state& state, uintptr_t offset ) { if ( offset == 0 ) return 0; - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; @@ -765,36 +852,50 @@ inline uintptr_t pam_pmm_get_elem_size( uintptr_t offset ) return slot->elem_size; } +/// Обёртка для обратной совместимости. +inline uintptr_t pam_pmm_get_elem_size( uintptr_t offset ) +{ + return pam_pmm_get_elem_size( pam_pmm_global_state(), offset ); +} + // ═══════════════════════════════════════════════════════════════════════════ // МЕТРИКИ // ═══════════════════════════════════════════════════════════════════════════ /** - * @brief Получить количество аллоцированных слотов. - * - * Возвращает число записей в slot_map_. + * @brief Получить количество аллоцированных слотов (явное состояние). */ -inline uintptr_t pam_pmm_slot_count() +inline uintptr_t pam_pmm_slot_count( pam_pmm_state& state ) { - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; return reg->slot_map_.size(); } +/// Обёртка для обратной совместимости. +inline uintptr_t pam_pmm_slot_count() +{ + return pam_pmm_slot_count( pam_pmm_global_state() ); +} + /** - * @brief Получить количество именованных объектов. - * - * Возвращает число записей в name_map_. + * @brief Получить количество именованных объектов (явное состояние). */ -inline uintptr_t pam_pmm_named_count() +inline uintptr_t pam_pmm_named_count( pam_pmm_state& state ) { - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg == nullptr ) return 0; return reg->name_map_.size(); } +/// Обёртка для обратной совместимости. +inline uintptr_t pam_pmm_named_count() +{ + return pam_pmm_named_count( pam_pmm_global_state() ); +} + /** * @brief Получить позицию bump-указателя (используемый размер). * @@ -893,7 +994,11 @@ inline uintptr_t pam_pmm_ptr_to_offset( const void* p ) * @note При успехе старый блок деаллоцируется автоматически. * При ошибке старый блок остаётся нетронутым. */ -template inline uintptr_t pam_pmm_realloc( uintptr_t old_offset, uintptr_t old_count, uintptr_t new_count ) +/** + * @brief Перевыделить память (явное состояние). + */ +template +inline uintptr_t pam_pmm_realloc( pam_pmm_state& state, uintptr_t old_offset, uintptr_t old_count, uintptr_t new_count ) { if ( old_offset == 0 || new_count == 0 ) return 0; @@ -923,7 +1028,7 @@ template inline uintptr_t pam_pmm_realloc( uintptr_t old_offset, ui PamManager::template deallocate_typed( old_pptr ); // Обновляем запись в slot_map_. - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( state ); if ( reg != nullptr ) { // Удаляем старую запись. @@ -935,7 +1040,7 @@ template inline uintptr_t pam_pmm_realloc( uintptr_t old_offset, ui reg->slot_map_.erase( old_offset ); // Добавляем новую запись. - reg = pam_pmm_get_registry(); + reg = pam_pmm_get_registry( state ); if ( reg != nullptr ) { old_info.offset = new_offset; @@ -947,6 +1052,12 @@ template inline uintptr_t pam_pmm_realloc( uintptr_t old_offset, ui return new_offset; } +/// Обёртка для обратной совместимости. +template inline uintptr_t pam_pmm_realloc( uintptr_t old_offset, uintptr_t old_count, uintptr_t new_count ) +{ + return pam_pmm_realloc( pam_pmm_global_state(), old_offset, old_count, new_count ); +} + /** * @brief Зарезервировать ёмкость (заглушка для совместимости). * @@ -959,13 +1070,17 @@ inline void pam_pmm_reserve_slots( uintptr_t /*min_slots*/ ) } /** - * @brief Валидация хранилища (заглушка). - * - * Проверяет базовую корректность состояния PMM. + * @brief Валидация хранилища (явное состояние). */ +inline bool pam_pmm_validate( const pam_pmm_state& state ) +{ + return state.initialized && PamManager::is_initialized(); +} + +/// Обёртка для обратной совместимости. inline bool pam_pmm_validate() { - return pam_pmm_is_initialized() && PamManager::is_initialized(); + return pam_pmm_validate( pam_pmm_global_state() ); } // ═══════════════════════════════════════════════════════════════════════════ @@ -1042,8 +1157,8 @@ inline void pstringview_pmm_restore_root() inline void pstringview_pmm_reset_restored_flag() { pstringview_pmm_root_restored_flag() = false; - detail::pam_pmm_initialized() = false; - detail::pam_pmm_root_offset() = 0; + pam_pmm_global_state().initialized = false; + pam_pmm_global_state().root_offset = 0; } /// Регистрация callbacks для персистентности корня AVL-дерева pstringview. diff --git a/pjson_db_pmm.h b/pjson_db_pmm.h index b328e9a..70f0164 100644 --- a/pjson_db_pmm.h +++ b/pjson_db_pmm.h @@ -95,13 +95,20 @@ class pjson_db_pmm /// Открыть или создать базу данных через PMM. static pjson_db_pmm open( const char* pam_file ) { - pam_pmm_init( pam_file ); + pam_pmm_init( pam_pmm_global_state(), pam_file ); return pjson_db_pmm{}; } - /// Конструктор по умолчанию: привязывается к текущему PMM. + /// Открыть или создать базу данных через PMM (явное состояние). + static pjson_db_pmm open( pam_pmm_state& state, const char* pam_file ) + { + pam_pmm_init( state, pam_file ); + return pjson_db_pmm{ state }; + } + + /// Конструктор по умолчанию: привязывается к глобальному состоянию PMM. /// PMM должен быть проинициализирован вызовом pam_pmm_init() или open(). - pjson_db_pmm() + pjson_db_pmm() : _state_ptr( &pam_pmm_global_state() ) { _ensure_pool(); _ensure_root(); @@ -109,6 +116,19 @@ class pjson_db_pmm _ensure_metrics_tmp(); // Этап 8.4: pre-allocate переиспользуемый узел для метрик } + /// Конструктор с явным состоянием PMM-фасада (Этап B, Issue #209). + explicit pjson_db_pmm( pam_pmm_state& state ) : _state_ptr( &state ) + { + _ensure_pool(); + _ensure_root(); + _get_metrics_struct(); // создаёт при необходимости + _ensure_metrics_tmp(); // Этап 8.4: pre-allocate переиспользуемый узел для метрик + } + + /// Получить ссылку на используемое состояние PMM. + pam_pmm_state& state() { return *_state_ptr; } + const pam_pmm_state& state() const { return *_state_ptr; } + // ----------------------------------------------------------------------- // Пакетные операции (batch) // ----------------------------------------------------------------------- @@ -376,7 +396,7 @@ class pjson_db_pmm _fill_metrics( m ); m->last_save_time = static_cast( std::time( nullptr ) ); } - pam_pmm_save(); + pam_pmm_save( *_state_ptr ); } // ----------------------------------------------------------------------- @@ -559,13 +579,17 @@ class pjson_db_pmm uintptr_t pmm_total_size() const { return pam_pmm_get_data_size(); } /// Получить количество слотов (объектов) в реестре. - uintptr_t pmm_slot_count() const { return pam_pmm_slot_count(); } + uintptr_t pmm_slot_count() const { return pam_pmm_slot_count( *_state_ptr ); } /// Получить количество именованных объектов. - uintptr_t pmm_named_count() const { return pam_pmm_named_count(); } + uintptr_t pmm_named_count() const { return pam_pmm_named_count( *_state_ptr ); } private: - uintptr_t _batch_depth = 0; ///< Глубина вложенности пакетных операций. + /// Состояние PMM-фасада (Этап B, Issue #209). + /// Указатель вместо ссылки, чтобы const-методы (get, find) могли читать + /// состояние через pam_pmm_get_registry(), возвращающий не-const указатель в ПАП. + pam_pmm_state* _state_ptr; + uintptr_t _batch_depth = 0; ///< Глубина вложенности пакетных операций. uintptr_t _metrics_tmp_off = 0; ///< Этап 8.4: pre-allocated узел для возврата значений метрик. // ----------------------------------------------------------------------- @@ -806,7 +830,7 @@ class pjson_db_pmm // Вспомогательные методы: пул и корень // ----------------------------------------------------------------------- - uintptr_t _find_pool_offset() const { return pam_pmm_find( PJSON_DB_PMM_POOL_NAME ); } + uintptr_t _find_pool_offset() const { return pam_pmm_find( *_state_ptr, PJSON_DB_PMM_POOL_NAME ); } pjson_pool_pmm* _get_pool() const { @@ -827,7 +851,7 @@ class pjson_db_pmm return; // Регистрируем пул в реестре имён PMM. - pam_pmm_registry* reg = pam_pmm_get_registry(); + pam_pmm_registry* reg = pam_pmm_get_registry( *_state_ptr ); if ( reg != nullptr ) { pam_pmm_name_key nk{}; @@ -842,7 +866,7 @@ class pjson_db_pmm } } - node_id _find_root() const { return pam_pmm_find( PJSON_DB_PMM_ROOT_NAME ); } + node_id _find_root() const { return pam_pmm_find( *_state_ptr, PJSON_DB_PMM_ROOT_NAME ); } void _ensure_root() { @@ -850,7 +874,7 @@ class pjson_db_pmm return; // Создаём корневой узел через PMM. - uintptr_t root_off = pam_pmm_create( PJSON_DB_PMM_ROOT_NAME ); + uintptr_t root_off = pam_pmm_create( *_state_ptr, PJSON_DB_PMM_ROOT_NAME ); if ( root_off == 0 ) return; @@ -860,10 +884,10 @@ class pjson_db_pmm /// Этап 8.4: создать или найти переиспользуемый временный узел для метрик. void _ensure_metrics_tmp() { - _metrics_tmp_off = pam_pmm_find( PJSON_DB_PMM_METRICS_TMP_NAME ); + _metrics_tmp_off = pam_pmm_find( *_state_ptr, PJSON_DB_PMM_METRICS_TMP_NAME ); if ( _metrics_tmp_off == 0 ) { - _metrics_tmp_off = pam_pmm_create( PJSON_DB_PMM_METRICS_TMP_NAME ); + _metrics_tmp_off = pam_pmm_create( *_state_ptr, PJSON_DB_PMM_METRICS_TMP_NAME ); if ( _metrics_tmp_off != 0 ) node_init_null( _metrics_tmp_off ); } @@ -1128,7 +1152,7 @@ class pjson_db_pmm if ( arr[idx] != 0 ) { - pam_pmm_delete( arr[idx] ); + pam_pmm_delete( *_state_ptr, arr[idx] ); } arr[idx] = value_id; @@ -1141,10 +1165,10 @@ class pjson_db_pmm db_metrics_pmm* _get_metrics_struct() { - uintptr_t off = pam_pmm_find( PJSON_DB_PMM_METRICS_NAME ); + uintptr_t off = pam_pmm_find( *_state_ptr, PJSON_DB_PMM_METRICS_NAME ); if ( off == 0 ) { - off = pam_pmm_create( PJSON_DB_PMM_METRICS_NAME ); + off = pam_pmm_create( *_state_ptr, PJSON_DB_PMM_METRICS_NAME ); if ( off == 0 ) return nullptr; db_metrics_pmm* mp = pmm_resolve( off ); @@ -1157,7 +1181,7 @@ class pjson_db_pmm const db_metrics_pmm* _get_metrics_struct_const() const { - uintptr_t off = pam_pmm_find( PJSON_DB_PMM_METRICS_NAME ); + uintptr_t off = pam_pmm_find( *_state_ptr, PJSON_DB_PMM_METRICS_NAME ); if ( off == 0 ) return nullptr; return pmm_resolve_const( off ); @@ -1177,8 +1201,8 @@ class pjson_db_pmm m->pam_bump_offset = static_cast( pam_pmm_get_bump() ); m->pam_free_list_size = 0; // PMM не имеет прямого эквивалента m->pam_total_size = static_cast( pam_pmm_get_data_size() ); - m->pam_slot_count = static_cast( pam_pmm_slot_count() ); - m->pam_named_count = static_cast( pam_pmm_named_count() ); + m->pam_slot_count = static_cast( pam_pmm_slot_count( *_state_ptr ) ); + m->pam_named_count = static_cast( pam_pmm_named_count( *_state_ptr ) ); // Метрики строк m->string_count_total = static_cast( pam_all_strings().size() ); diff --git a/plan.md b/plan.md index 157d41a..5c05200 100644 --- a/plan.md +++ b/plan.md @@ -21,7 +21,7 @@ | 7. Унификация итераторов | CRTP-база pjson_iterator_base + шаблонный pjson_range; ~22 строки удалено | ✅ | **Итого удалено:** ~5 файлов (~1900 строк), ~381 строка дублирования. -**Тесты:** 700 тестов, ~360 000 assertion. +**Тесты:** 709 тестов, ~360 000 assertion. --- @@ -45,7 +45,7 @@ **Остающиеся этапы (будущие задачи):** 1. ~~**Этап A:** Инкапсулировать три переменные в структуру `pam_pmm_state`~~ ✅ -2. **Этап B:** Передавать `pam_pmm_state&` как явный параметр вместо обращения к глобальным переменным +2. ~~**Этап B:** Передавать `pam_pmm_state&` как явный параметр вместо обращения к глобальным переменным~~ ✅ 3. **Этап C:** Опционально — защита `std::mutex` для потокобезопасной инициализации --- @@ -210,6 +210,9 @@ pvector был бы предпочтительнее **только** при ч 10.2 ✅ Поддержка RFC 6901 (JSON Pointer) для путей 10.3 ✅ Оптимизация tag-проверок на горячих путях 10.4 ✅ const-корректность _walk_path: разделение на _walk_path_read (const) и _walk_path_create + +Этап 11: Проблема 3 — Этап B (явный параметр состояния) + 11.1 ✅ pam_pmm_state& как явный параметр всех pam_pmm_* функций; pjson_db_pmm хранит ссылку ``` --- @@ -218,6 +221,7 @@ pvector был бы предпочтительнее **только** при ч | Дата | Изменение | |------|-----------| +| 2026-03-22 | Этап 11.1: pam_pmm_state& как явный параметр pam_pmm_* функций; pjson_db_pmm хранит ссылку на состояние (Issue #209) | | 2026-03-22 | Этап 10.4: const-корректность _walk_path — разделение на _walk_path_read (const) и _walk_path_create (Issue #208) | | 2026-03-22 | Этап 10.3: оптимизация tag-проверок на горячих путях — сокращение избыточных pmm_resolve (Issue #207) | | 2026-03-22 | Этап 10.2: поддержка RFC 6901 (JSON Pointer) для путей — escaping ~/slash в ключах (Issue #206) | diff --git a/readme.md b/readme.md index 00de5ba..788cd5d 100644 --- a/readme.md +++ b/readme.md @@ -139,7 +139,7 @@ int main() { | `pjson_db_pmm.h` | D | Менеджер персистной JSON-БД: path-адресация, `put`/`get`/`erase`, `$ref`, метрики, поиск, клонирование | | `deps/pmm/pmm.h` | A | [PersistMemoryManager](https://github.com/netkeep80/PersistMemoryManager) — бэкенд ПАП | | `main.cpp` | — | Демонстрационная программа | -| `tests/` | — | Тесты на Catch2 (700 тестов, ~360 000 assertion) | +| `tests/` | — | Тесты на Catch2 (709 тестов, ~360 000 assertion) | | `CMakeLists.txt` | — | Система сборки (CMake 3.16+, C++20) | --- @@ -451,7 +451,7 @@ db.put("/copy/name", "Bob"); ## Известные ограничения -- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state`, передача как параметра — в будущих версиях +- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state` (Этап A), все `pam_pmm_*` функции принимают `pam_pmm_state&` как явный параметр (Этап B), `pjson_db_pmm` хранит ссылку на состояние; PamManager остаётся глобальным синглтоном - ~~**Нет escaping `/` в путях**~~ — **Исправлено** в Этапе 10.2: поддержка RFC 6901 (JSON Pointer) — `~1` для `/`, `~0` для `~` в сегментах путей - ~~**Утечка временных узлов метрик**~~ — **Исправлено** в Этапе 8.4: один pre-allocated узел переиспользуется для всех вызовов метрик - ~~**Многократный resolve в is_*() проверках**~~ — **Исправлено** в Этапе 10.3: `is_number()`, `deref()`, traversal и walk_path оптимизированы для единственного `pmm_resolve` вместо повторных вызовов diff --git a/tests/test_pam_pmm.cpp b/tests/test_pam_pmm.cpp index 1531272..a765317 100644 --- a/tests/test_pam_pmm.cpp +++ b/tests/test_pam_pmm.cpp @@ -16,6 +16,7 @@ #include #include "pam_pmm.h" +#include "pjson_db_pmm.h" using namespace pjson; @@ -592,3 +593,180 @@ TEST_CASE( "pam_pmm_state: init/destroy cycle reflected in global state", "[pam_ REQUIRE( gs.root_offset == 0 ); REQUIRE( gs.filename[0] == '\0' ); } + +// ═══════════════════════════════════════════════════════════════════════════ +// ТЕСТЫ явного параметра pam_pmm_state (Этап B, Issue #209) +// ═══════════════════════════════════════════════════════════════════════════ + +TEST_CASE( "pam_pmm_state explicit: init/destroy with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + REQUIRE( gs.initialized == true ); + REQUIRE( gs.root_offset != 0 ); + REQUIRE( pam_pmm_is_initialized( gs ) == true ); + + pam_pmm_destroy( gs ); + + REQUIRE( gs.initialized == false ); + REQUIRE( gs.root_offset == 0 ); +} + +TEST_CASE( "pam_pmm_state explicit: create/find/delete with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + // create with explicit state + uintptr_t off = pam_pmm_create( gs, "test_explicit_obj" ); + REQUIRE( off != 0 ); + + // find with explicit state + uintptr_t found = pam_pmm_find( gs, "test_explicit_obj" ); + REQUIRE( found == off ); + + // find_typed with explicit state + uintptr_t found_typed = pam_pmm_find_typed( gs, "test_explicit_obj" ); + REQUIRE( found_typed == off ); + + // get_name with explicit state + const char* name = pam_pmm_get_name( gs, off ); + REQUIRE( name != nullptr ); + REQUIRE( std::strcmp( name, "test_explicit_obj" ) == 0 ); + + // get_elem_size with explicit state + REQUIRE( pam_pmm_get_elem_size( gs, off ) == sizeof( uint64_t ) ); + + // get_count with explicit state + REQUIRE( pam_pmm_get_count( gs, off ) == 1 ); + + // delete with explicit state + uintptr_t slot_before = pam_pmm_slot_count( gs ); + pam_pmm_delete( gs, off ); + REQUIRE( pam_pmm_find( gs, "test_explicit_obj" ) == 0 ); + REQUIRE( pam_pmm_slot_count( gs ) == slot_before - 1 ); + + pam_pmm_destroy( gs ); +} + +TEST_CASE( "pam_pmm_state explicit: create_array with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + uintptr_t off = pam_pmm_create_array( gs, 10, "test_explicit_arr" ); + REQUIRE( off != 0 ); + + REQUIRE( pam_pmm_find( gs, "test_explicit_arr" ) == off ); + REQUIRE( pam_pmm_get_count( gs, off ) == 10 ); + REQUIRE( pam_pmm_get_elem_size( gs, off ) == sizeof( uint32_t ) ); + + pam_pmm_destroy( gs ); +} + +TEST_CASE( "pam_pmm_state explicit: get_root and get_registry with explicit state", + "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + pam_pmm_root* root = pam_pmm_get_root( gs ); + REQUIRE( root != nullptr ); + REQUIRE( root->magic == PAM_PMM_MAGIC ); + REQUIRE( root->version == PAM_PMM_VERSION ); + + pam_pmm_registry* reg = pam_pmm_get_registry( gs ); + REQUIRE( reg != nullptr ); + + pam_pmm_destroy( gs ); +} + +TEST_CASE( "pam_pmm_state explicit: reset with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + // Создаём объект. + pam_pmm_create( gs, "before_reset" ); + REQUIRE( pam_pmm_find( gs, "before_reset" ) != 0 ); + + // Reset с явным состоянием. + pam_pmm_reset( gs ); + REQUIRE( gs.initialized == true ); + REQUIRE( gs.root_offset != 0 ); + + // Объект из предыдущего хранилища должен быть недоступен. + REQUIRE( pam_pmm_find( gs, "before_reset" ) == 0 ); + + pam_pmm_destroy( gs ); +} + +TEST_CASE( "pam_pmm_state explicit: slot_count and named_count with explicit state", + "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + uintptr_t slots_before = pam_pmm_slot_count( gs ); + uintptr_t named_before = pam_pmm_named_count( gs ); + + pam_pmm_create( gs, "explicit_metric_test" ); + + REQUIRE( pam_pmm_slot_count( gs ) == slots_before + 1 ); + REQUIRE( pam_pmm_named_count( gs ) == named_before + 1 ); + + // Результаты должны совпадать с обёртками без параметра. + REQUIRE( pam_pmm_slot_count( gs ) == pam_pmm_slot_count() ); + REQUIRE( pam_pmm_named_count( gs ) == pam_pmm_named_count() ); + + pam_pmm_destroy( gs ); +} + +TEST_CASE( "pam_pmm_state explicit: validate with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + REQUIRE( pam_pmm_validate( gs ) == true ); + REQUIRE( pam_pmm_validate( gs ) == pam_pmm_validate() ); + + pam_pmm_destroy( gs ); + + REQUIRE( pam_pmm_validate( gs ) == false ); +} + +TEST_CASE( "pam_pmm_state explicit: pjson_db_pmm with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + pam_pmm_init( gs, nullptr ); + + // Создаём БД с явным состоянием. + pjson_db_pmm db( gs ); + + REQUIRE( &db.state() == &gs ); + + db.put( "/test/key", 42 ); + node_view v = db.get( "/test/key" ); + REQUIRE( v.is_integer() ); + REQUIRE( v.as_int() == 42 ); + + pam_pmm_destroy( gs ); +} + +TEST_CASE( "pam_pmm_state explicit: pjson_db_pmm::open with explicit state", "[pam_pmm][pam_pmm_state][explicit]" ) +{ + pam_pmm_state& gs = pam_pmm_global_state(); + + auto db = pjson_db_pmm::open( gs, nullptr ); + + REQUIRE( &db.state() == &gs ); + REQUIRE( gs.initialized == true ); + + db.put( "/hello", "world" ); + node_view v = db.get( "/hello" ); + REQUIRE( v.is_string() ); + REQUIRE( v.as_string() == "world" ); + + pam_pmm_destroy( gs ); +}