FOnline Engine
Current 2026.1.58-dev GitHub
Документация Docs/ru/reference/native/essentials.md

Базовый слой Essentials

Документация движка. Эта страница описывает низкоуровневый слой Source/Essentials/: требования к платформе и компилятору, вспомогательные средства жизненного цикла процесса, журналирование, память, строки, сериализацию, файловую систему, сокеты и базовые типы, используемые всеми вышележащими слоями движка.

Назначение

Используйте эту страницу при изменении кода ниже Source/Common/ и когда нужно определить, относится ли новая утилита к переиспользуемому фундаменту движка, а не к клиенту, серверу, инструментам или конкретной игре.

Контракт обработки исключений в модели памяти описан в разделе Безопасность исключений: SafeAlloc / SafeAllocator завершают процесс при нехватке памяти, поэтому std::bad_alloc не является восстанавливаемой ошибкой, а уровни throw / FO_VERIFY_* / FO_STRONG_ASSERT строятся поверх ExceptionHandling.h.

Слой Essentials должен сохранять минимум зависимостей. Большая часть движка подключает его через Source/Essentials/Essentials.h, поэтому изменение здесь способно затронуть каждое приложение.

Решение между слоями

Essentials следует строгому dependency DAG: зависимость должна находиться раньше в umbrella block, а обратную зависимость следует передать вверх через параметры или более высокий владеющий слой. Регистрируйте новые implementation files в FO_ESSENTIALS_SOURCE; владеющей целью является EssentialsLib, а consumers должны линковаться в correct dependency point, не обходя слой.

Точный umbrella order: BasicCore, GlobalData, StackTrace, BaseLogging, FatalError, FunctionObjects, SmartPointers, MemorySystem, StringObject, DequeObject, Containers, StringUtils, WinApi, Posix, Platform, ExceptionHandling, RandomGenerator, Threading, SafeArithmetics, DataSerialization, HashedString, StrongType, TimeRelated, ExtendedTypes, Compressor, WorkThread, Logging, DiskFileSystem, CommonHelpers и NetSockets. Не меняйте этот список местами для исправления cycle. Передавайте reverse dependency pressure вверх через parameter или разделяйте ответственность в более высоком слое-владельце. Каждый новый Essentials .h / .cpp должен войти в проверяемый список FO_ESSENTIALS_SOURCE, из которого CoreLibs.cmake создаёт EssentialsLib; inventory путей исходников сам по себе не является build wiring. В umbrella order Essentials.h участвуют только headers; никогда не добавляйте туда .cpp. Регистрируйте и headers, и implementation files в FO_ESSENTIALS_SOURCE, затем проверяйте, что ими владеет EssentialsLib, а его consumers линкуются в correct dependency point.

Когда то же изменение затрагивает script-visible metadata, разделяйте владение. Engine владеет reusable metadata/codegen machinery; embedding project предоставляет project configuration, дополнительные metadata sources, common headers и script/content inputs; generated files являются build artifacts. Сравнивайте все восемнадцать canonical generated models и требуйте проверенную точную domain-bound disposition для каждого gated compatibility break; unit test Essentials не обходит contract-change gate.

Проверенные исходные пути

  • Source/Essentials/Essentials.h
  • Source/Essentials/Essentials.cpp
  • Source/Essentials/BasicCore.h
  • Source/Essentials/BasicCore.cpp
  • Source/Essentials/GlobalData.h
  • Source/Essentials/GlobalData.cpp
  • Source/Essentials/StackTrace.h
  • Source/Essentials/StackTrace.cpp
  • Source/Essentials/BaseLogging.h
  • Source/Essentials/BaseLogging.cpp
  • Source/Essentials/FatalError.h
  • Source/Essentials/FatalError.cpp
  • Source/Essentials/FunctionObjects.h
  • Source/Essentials/FunctionObjects.cpp
  • Source/Essentials/SmartPointers.h
  • Source/Essentials/SmartPointers.cpp
  • Source/Essentials/MemorySystem.h
  • Source/Essentials/MemorySystem.cpp
  • Source/Essentials/StringObject.h
  • Source/Essentials/StringObject.cpp
  • Source/Essentials/DequeObject.h
  • Source/Essentials/DequeObject.cpp
  • Source/Essentials/Containers.h
  • Source/Essentials/Containers.cpp
  • ThirdParty/small_vector/README.md
  • ThirdParty/small_vector/source/include/gch/small_vector.hpp
  • Source/Essentials/StringUtils.h
  • Source/Essentials/StringUtils.cpp
  • Source/Essentials/WinApi.h
  • Source/Essentials/WinApi.cpp
  • Source/Essentials/Posix.h
  • Source/Essentials/Posix.cpp
  • Source/Essentials/Platform.h
  • Source/Essentials/Platform.cpp
  • Source/Essentials/ExceptionHandling.h
  • Source/Essentials/ExceptionHandling.cpp
  • Source/Essentials/RandomGenerator.h
  • Source/Essentials/RandomGenerator.cpp
  • Source/Essentials/Cryptography.h
  • Source/Essentials/Cryptography.cpp
  • ThirdParty/Monocypher/src/monocypher.h
  • ThirdParty/Monocypher/src/monocypher.c
  • Source/Essentials/Threading.h
  • Source/Essentials/Threading.cpp
  • Source/Essentials/SafeArithmetics.h
  • Source/Essentials/SafeArithmetics.cpp
  • Source/Essentials/DataSerialization.h
  • Source/Essentials/DataSerialization.cpp
  • Source/Essentials/HashedString.h
  • Source/Essentials/HashedString.cpp
  • Source/Essentials/StrongType.h
  • Source/Essentials/StrongType.cpp
  • Source/Essentials/TimeRelated.h
  • Source/Essentials/TimeRelated.cpp
  • Source/Essentials/ExtendedTypes.h
  • Source/Essentials/ExtendedTypes.cpp
  • Source/Essentials/Compressor.h
  • Source/Essentials/Compressor.cpp
  • Source/Essentials/WorkThread.h
  • Source/Essentials/WorkThread.cpp
  • Source/Essentials/Logging.h
  • Source/Essentials/Logging.cpp
  • Source/Essentials/DiskFileSystem.h
  • Source/Essentials/DiskFileSystem.cpp
  • Source/Essentials/CommonHelpers.h
  • Source/Essentials/CommonHelpers.cpp
  • Source/Essentials/NetSockets.h
  • Source/Essentials/NetSockets.cpp
  • Source/Essentials/UcsTables.inc
  • Source/Essentials/WinApiUndef.inc
  • BuildTools/natvis/essentials.natvis
  • BuildTools/cmake/stages/EngineSources.cmake
  • BuildTools/tests/test_essentials_layering.py
  • связанные тесты в Source/Tests/

Модель подключений и зависимостей

Source/Essentials/Essentials.h является общим umbrella-заголовком. Его точный include order одновременно задаёт dependency order фундаментального слоя:

BasicCore → GlobalData → StackTrace → BaseLogging → FatalError → FunctionObjects → SmartPointers → MemorySystem → StringObject → DequeObject → Containers → StringUtils → WinApi → Posix → Platform → ExceptionHandling → RandomGenerator → Threading → SafeArithmetics → DataSerialization → HashedString → StrongType → TimeRelated → ExtendedTypes → Compressor → Cryptography → WorkThread → Logging → DiskFileSystem → CommonHelpers → NetSockets.

Этот список намеренно точный, а не тематический. Essentials.h задаёт строгий DAG зависимостей: каждый заголовок Essentials и соответствующий .cpp может подключать и вызывать только modules, расположенные в umbrella-блоке выше него. Объявление API в раннем header с определением в более позднем .cpp всё равно создаёт обратную link dependency. BuildTools/tests/test_essentials_layering.py проверяет прямые includes и ownership внешних namespace-level definitions. Не меняйте порядок ради сокрытия цикла; передайте данные параметром или разделите ответственность на правильной границе слоёв.

Новые API Essentials не должны зависеть от Source/Common/, Source/Client/, Source/Server/, Source/Tools/ или заголовков встраиваемого проекта.

global_data::destroy(observer, context) передаёт observer имя каждого зарегистрированного набора непосредственно перед его delete callback. Так зависший teardown можно связать с набором, не позволяя observer зависеть от уже уничтоженных global data. Host/runtime module уничтожает только созданный им набор и присоединяет свои workers до возврата в продолжающий работу host. Process-lifetime crash-record state остаётся вне этого sweep.

Карта подсистем

Граница платформы и компилятора

BasicCore.h также объявляет имена FO_TRACE_COLOR_<Category> и макросы FO_TRACE_ZONE(Category) / FO_TRACE_ZONE_NAMED(Category, name). Профилирующие сборки используют generated TraceCategories.gen.h для выбора зон; остальные проверяют имена категорий, но не создают зон. См. профилирование.

BasicCore.h проверяет выбранный макрос ОС (FO_WINDOWS, FO_LINUX, FO_MAC, FO_ANDROID, FO_IOS или FO_WEB) и требует C++20. Здесь же часто используемые стандартные типы вводятся в namespace движка и объявляются базовые макросы, включая FO_EXPORT_FUNC, FO_KEEP_DATA_SYMBOL и helpers для namespace. Средства подавления предупреждений также находятся здесь: FO_DISABLE_WARNINGS_PUSH/POP отключает все предупреждения при обёртывании third-party headers, а пары FO_GCC_IGNORE_WARNINGS_PUSH/POP, FO_CLANG_IGNORE_WARNINGS_PUSH/POP и FO_MSVC_IGNORE_WARNINGS_PUSH/POP подавляют одно именованное предупреждение только в соответствующем компиляторе. Это позволяет изолировать false positive одного toolchain, не заставляя остальные отвергать неизвестный номер -W или warning. Сначала исправляйте причину предупреждения; per-compiler helper допустим только для документированного false positive компилятора.

Platform.h / .cpp владеет небольшим набором host-specific helpers: информационным журналированием, именами потоков, поиском пути executable и пользовательского каталога данных, форматированием process id, fork там, где он доступен, использованием памяти процессом, CPU snapshots и загрузкой динамических модулей. Platform::GetUserDataBase() предпочитает окружение и не использует shell/SDL: Windows берёт %LOCALAPPDATA% с fallback на %APPDATA%, macOS/iOS использует $HOME/Library/Application Support, Linux/Android/прочие платформы используют $XDG_DATA_HOME с fallback на $HOME/.local/share. Если окружение не задаёт путь, Windows использует SHGetKnownFolderPath, а поддерживаемые POSIX hosts — getpwuid_r; host без обоих источников не возвращает путь. Вышележащий слой добавляет имя приложения и решает, является ли отсутствие пути фатальным. Platform::GetCpuUsageSnapshot() возвращает накопительные системные счётчики по ядрам и CPU time текущего процесса; вызывающий код сравнивает два snapshot для вычисления процентов и хранит sampling/cache state вне Platform. Platform находится выше ExceptionHandling и использует более ранний FO_BASIC_STRONG_ASSERT для terminating host-API invariants, не импортируя поздние exception macros. Platform-specific поведение приложения, окна и рендеринга находится в Source/Frontend/.

platform::get_last_module_error() сразу после неудачной загрузки модуля или поиска символа возвращает текст ошибки загрузчика Windows либо POSIX dlerror(). platform::get_os_version() получает версию ОС через RtlGetVersion в Windows и uname на остальных поддерживаемых системах для диагностики клиента.

WinApi.* и Posix.* владеют вызовами операционной системы за пространствами имён winapi:: и posix::. Их публичные границы используют строки движка, optional, целые типы фиксированной ширины и nptr<void>, не выпуская наружу HANDLE, pid_t и другие OS-типы. Platform диспетчеризует вызовы в эти модули; обычный потребитель добавляет или вызывает wrapper, а не подключает <Windows.h> либо POSIX-заголовки напрямую. Структурные исключения — нижние реализации Essentials, которые не могут зависеть от этих модулей из-за порядка слоёв (BasicCore.cpp, BaseLogging.cpp, StringUtils.cpp), а также NetSockets.* и ServerServiceApp.cpp, сами являющиеся OS wrappers.

В Darwin SDK операции над наборами сигналов могут быть функциональными макросами. Внутри Posix.cpp вызывайте sigfillset, sigdelset, sigemptyset и sigaddset без квалификатора ::, чтобы эти определения раскрывались корректно; в Linux по-прежнему находятся глобальные функции. Маски, флаги, альтернативный стек и повторное возбуждение сигнала с действием по умолчанию в обработчике сбоев сохраняют прежнее поведение. Обычные потребители продолжают использовать обёртки posix::, а не напрямую подключать заголовки ОС или вызывать её функции.

Windows builds сохраняют compile baseline _WIN32_WINNT=0x0601. Единый registry Windows build platforms владеет архитектурой CMake, toolset и канонической packaging-архитектурой обычных вариантов, -clang и -win7. Пара Win7 фиксирует MSVC 14.44, а FO_BINARY_OUTPUT_POSTFIX остаётся независимым от платформы. В package DSL конкретная запись BINARY может выбрать собственный postfix, например BINARY Client Windows win32-win7 Raw+Zip+Wix POSTFIX Win7, не затрагивая соседние binaries. Проверки совместимости находятся вне application targets.

platform::process_identity объединяет PID и время запуска процесса. Один ID может быть использован повторно, поэтому диагностика клиентской сессии сверяет оба поля. В Windows liveness проверяется zero-timeout polling process handle, а не кодом выхода 259 (STILL_ACTIVE), который может сохраняться у завершённого процесса при удержании handle другим процессом. BuildTools/tests/test_process_identity.py проверяет живой и завершённый retained-handle случаи при наличии clang++.

Временная совместимость

Код обработки старых сборок/данных использует FO_TEMPORARY_COMPAT(Id, "YYYY-MM-DD"); (native, BasicCore.h) или [TemporaryCompat("Id", "YYYY-MM-DD")] (managed, CoreScripts/Attributes.cs). Один идентификатор и дата повторяются в реализациях, полях и тестах. Native static_assert проверяет форму даты в namespace/классе/блоке.

CMake использует TemporaryCompat(Id "YYYY-MM-DD") из BuildTools/cmake/helpers/Commands.cmake. Помечайте временные отказы/исключения зависимостей; постоянные условия не требуют даты. Helper проверяет форму аргументов и даты. Удаляйте только временное исключение после проверки поддержки платформы.

BuildTools/temporary_compat.py и его тесты запускаются в CI. По умолчанию сканируются Source/ и BuildTools/cmake/ (.cmake, CMakeLists.txt, неактивные ветки); проекты передают файлы/каталоги. Комментарии/строки CMake пропускаются. Неверные/несовпадающие даты и сроки более 366 дней вызывают ошибку; после истечения проверка падает со следующего дня с перечнем всех мест. Просроченный код/условия удаляют либо продлевают срок проверенным изменением. Сборки сохраняют воспроизводимость.

Диагностика и обработка сбоев

BaseLogging.* и Logging.* образуют фундамент журналирования. WriteLogMessage() объединяет последовательные дубликаты с одинаковыми LogType и текстом: повторения пропускаются, а перед следующей отличающейся строкой выводится сводка вида ...and 25 more same messages. LogToFile() открывает файл без exclusive lock в рамках поведения платформы: std::ofstream MSVC использует deny-none, а POSIX не вводит обязательную блокировку при открытии. Благодаря этому два модуля движка в одном процессе, например runtime host EXE и загруженная им runtime DLL со своей копией engine global data, могут одновременно держать один файл открытым. Перед каждой записью WriteSync перемещается в конец файла, поэтому один handle не перезапишет данные, добавленные другим; параметр append по-прежнему выбирает truncate по умолчанию либо append при первоначальном открытии.

WriteLog / WriteBaseLog безопасно деградируют, если global data ещё не созданы: сначала переходят к base log, затем к std::cout.

FatalError.* является ранним native-only fatal layer. Он следует за StackTrace и BaseLogging, приостанавливает asynchronous writes, пишет одно синхронное сообщение с native trace и передаёт BasicCore::ExitApp(false) только механическое завершение. Ему принадлежат ReportFatalAndExit, ReportStrongAssertAndExit и FO_BASIC_STRONG_ASSERT; слой не создаёт exception objects и не зависит от более позднего ExceptionHandling. Сам ExitApp(false) остаётся status-only, поскольку его используют и контролируемые command failures, и fatal invariant failures.

StackTrace.* собирает и форматирует native/script stacks, а ExceptionHandling.* владеет более поздними helpers отчётов об exception objects. Debugger-сценарии описаны в разделе Native-, AngelScript- и Managed-отладка.

Память, указатели и время жизни

MemorySystem.* владеет резервными блоками памяти, отчётами о failed allocation и SafeAllocator. SmartPointers.* содержит wrappers, явно выражающие владение, nullability и назначение raw reference; словарь ptr / nptr и правила миграции приведены в разделе Умные указатели. В этом слое должны находиться только общие средства владения; время жизни entity и holder semantics принадлежат модели сущностей.

Словарь callable

FunctionObjects.* заменяет std::function двумя wrappers движка. function<Signature> является alias move-only типа move_only_function<Signature> и используется по умолчанию. copyable_function<Signature> нужен только когда копирование stored target действительно входит в ownership contract, например при snapshot callback для нескольких owners. Оба хранят небольшой nothrow-movable target inline, а крупный выделяют через fail-fast path, поэтому создание не вводит recoverable std::bad_alloc. Если migration обнаружил копирование, сначала проверьте, не должен ли owner выполнить move. Единственный оставшийся std::function — hook script provider в StackTrace.h, расположенный до FunctionObjects в порядке зависимостей.

Словарь выделения памяти

Код движка выделяет память только через две поверхности:

  • Псевдонимы контейнеров fo из Containers.h: string, wstring, vector, map, unordered_map, set, list, deque, stringstream, small_vector и связанные типы. string и wstring используют engine basic_string из StringObject.h, deque использует basic_deque из DequeObject.h, остальные allocator-aware aliases используют SafeAllocator. Используйте их вместо вариантов из std::.
  • SafeAlloc: MakeUnique / MakeShared / MakeRefCounted / MakeRawArr / MakeUniqueArr для типизированных объектов и raw-уровень MallocRaw / CallocRaw / ReallocRaw / FreeRaw, а также MallocAlignedRaw / FreeAlignedRaw для C ABI.

Raw-уровень нужен из-за C-образных allocator hooks third-party библиотек: они требуют realloc, нетипизированный блок байтов или оба варианта, что невозможно выразить C++ allocator. Он сохраняет ту же политику нехватки памяти, что и SafeAllocator: сообщить об ошибке, освободить резервный пул, повторить попытку и детерминированно завершить процесс. Поэтому подключение библиотеки через этот путь не выводит её из общего контракта. Запрос нулевого размера передаётся нижнему allocator, а не трактуется как ошибка.

Примитивы rpmalloc намеренно не экспортируются из MemorySystem.h. Они возвращают null при сбое и создали бы вторую доступную точку входа, обходящую контракт, поэтому остаются file-local statics в MemorySystem.cpp. Vendored allocator возвращает null и при on-demand commit зарезервированного span, и при неудачном recommit ранее decommitted свободной страницы. Страница recommit-ится до публикации как доступная; при отказе она возвращается в free list, поэтому повторная попытка после освобождения резервного пула не выдаст reserve-only память. Test_MemorySystem.cpp инъецирует оба сбоя. Отчёты о нехватке памяти сериализуются между потоками; если построение stack trace само не может выделить память, вложенный отчёт пишет только заголовок без рекурсии. Освобождённые другим потоком резервные chunks возвращаются в исходный thread heap и не гарантируют память каждому потоку. Lock-free allocator Mono находится вне этого контракта и может вызвать abort при OOM. Операции над блоками MemCopy / MemMove / MemFill / MemCompare / MemReadUnaligned / MemWriteUnaligned не выделяют память и остаются публичными.

Vendored rpmalloc сохраняет upstream spans размером 256 MiB на 64-bit targets. На 32-bit targets один span уменьшен до LARGE_PAGE_SIZE (16 MiB). Старые Windows allocation APIs резервируют size + alignment, поэтому aligned span 256 MiB может потребовать contiguous hole размером 512 MiB в 2 GiB x86 address space и сорвать уже первое небольшое allocation. Span 16 MiB на x86 сохраняет все встроенные page classes и устраняет startup-зависимость от одной огромной непрерывной reservation.

При обходе этого словаря возникают три разных последствия, и их тяжесть различается:

  Фактическое поведение
Отдельная куча Глобальные operator new / delete заменены на rpmalloc, поэтому любой new и std::allocator уже попадает в engine heap. Но rpmalloc собирается с ENABLE_OVERRIDE=0, C malloc / free не перехватываются, и использующие их библиотеки остаются в CRT heap, вне rpmalloc, статистики AllocatorGetInUseBytes() и Tracy allocation tracking.
Неверная политика нехватки памяти std::allocator бросает std::bad_alloc вместо terminate-on-OOM модели из раздела Безопасность исключений, пункт 1.
Выравнивание SafeAllocator направляет over-aligned element types через aligned-перегрузки operator new / delete. Проверка over-alignment должна оставаться member-функцией: alignof(T) требует полного T, но allocator обязан работать с неполным типом, поскольку std::vector<T> может быть объявлен до определения T.

Известные допустимые ограничения: std::future / std::promise / std::packaged_task, std::thread, std::filesystem::path и файловые streams не принимают allocator. Они попадают в engine heap через global new, но бросают исключение при исчерпании памяти. Единственный std::function в StackTrace.h также расположен до callable module движка. Отдельно BasicCore, StackTrace и BaseLogging расположены до MemorySystem в порядке Essentials.h и поэтому намеренно используют контейнеры std::: MemorySystem.cpp вызывает GetStackTrace() из ReportBadAlloc, и reporting path не должен зависеть от allocator, который только что отказал.

Диагностика заполнения аллокатора

memory::get_allocator_statistics() возвращает снимок без выделений памяти. При rpmalloc он доступен в Debug и Tracy либо с FO_MEMORY_DIAGNOSTICS=ON в обычной сборке; иначе available равен false. Общий экспорт AngelScript/Managed C# Game.GetAllocatorStatistics() возвращает пустой словарь при недоступности. Сам скриптовый словарь выделяет память, в отличие от native snapshot.

  • mappedBytes, committedBytes, hugeAllocatedBytes, heapCount относятся к экземпляру rpmalloc вызывающего native module. Commitment — global.active, не накопленный объём операций commit.
  • threadSizeClassAllocatedBytes — занятая ёмкость блоков вызывающего потока, включая округление и ещё не обработанные cross-thread frees, без huge allocations. Это не размер запрошенных данных и не live bytes всего процесса.
  • threadReusableBlockBytes включает немедленно доступные слоты и неиспользованные хвосты страниц. threadFreeCommittedPageBytes отдельно считает cached committed pages.
  • class<block-size>AllocatedBlocks и class<block-size>ReusableBlocks сохраняют распределение классов размера; меньший слот обычно не обслужит больший запрос.

Снимок не обходит чужие live heaps, не обрабатывает deferred frees, не вызывает GC и не чистит caches. Глобальные atomic counters не образуют единого атомарного снимка. CRT allocations, GC-object storage, драйверы, padding и GPU fragmentation не измеряются. Нельзя вычитать thread occupancy из global commitment для оценки фрагментации; свободная повторно используемая ёмкость сама по себе не доказывает вредную фрагментацию. Test_MemorySystem.cpp проверяет holes/reuse в потоке-владельце без очистки caches.

Vendored vkd3d-shader не предоставляет allocator hook. Его вызовы при запекании используют обычный malloc, а возвращённые объекты освобождаются собственными функциями vkd3d; ошибка прерывает запекание эффекта. Эти временные выделения не относятся к Engine heap и событиям Tracy allocator-а.

Allocators внешних библиотек

Библиотека Направляется в Место
ImGui SafeAllocator Common/ImGuiExt/ImGuiStuff.cpp
AngelScript SafeAllocator Scripting/AngelScript/AngelScriptScripting.cpp
zlib SafeAllocator Essentials/Compressor.cpp
ozz-animation aligned-уровень SafeAlloc Common/ModelAnimationData.cpp
meshoptimizer SafeAllocator Tools/ModelMeshBaker.cpp
ufbx SafeAllocator compile-time UFBX_EXTERNAL_MALLOC и extern "C" ufbx_malloc/realloc/free в Tools/ModelMeshBaker.cpp
SDL safe_alloc::*_raw Frontend/Application.cpp
Effekseer safe_alloc::*_raw + aligned Client/EffekseerExtension.cpp, объявление в его header; оба владельца, client runtime и Tools/ParticleBaker.cpp, устанавливают callbacks через одно определение
libpng safe_alloc::*_raw Tools/ImageBaker-Png.cpp через png_create_read_struct_2
libbson / mongo-c safe_alloc::*_raw + aligned Server/DataBase-Mongo.cpp; только фабрика Mongo устанавливает process-global vtable до создания backend
SQLite safe_alloc::*_raw Server/DataBase-SQLite.cpp через sqlite3_config(SQLITE_CONFIG_MALLOC) до sqlite3_initialize()

Форму bson vtable нужно изучить до её копирования в другую интеграцию. Она предоставляет aligned_alloc, но освобождает полученные блоки через обычный member free, не запоминая alignment. Это корректно, только пока оба пути используют одну release-функцию. В rpmalloc это так: rpaligned_alloc и rpmalloc завершаются в rpfree. То же верно на POSIX без rpmalloc, где блоки posix_memalign по определению освобождаются через free(). Ломается только Windows без rpmalloc, то есть sanitizer configurations, в которых expr_RpmallocEnabled отключает allocator ради interposition sanitizer: aligned-путь там использует _aligned_malloc / _aligned_free.

Поэтому BsonAlignedAlloc ровно в этом случае переходит к обычному safe_alloc::malloc_raw. Так делает и default vtable bson под MSVC по той же явно указанной причине: _aligned_alloc_impl в libbson memory.c намеренно не вызывает _aligned_malloc. Все aligned-запросы mongoc используют BSON_ALIGNOF обычной C-структуры, для которой fundamental alignment malloc достаточен. Vtable является process-global, поэтому фабрика Mongo устанавливает одинаковые callbacks до того, как backend сможет выделить память; поздняя замена могла бы сопоставить старый allocation новому free callback. Удаление aligned_alloc из vtable не является решением: bson подставит внутренний fallback, отбрасывающий требуемое alignment на всех платформах, а не только на проблемной.

Hook SQLite требует callback xSize и передаёт функциям free/realloc/size только указатель, поэтому каждый блок несёт 8-байтовый заголовок размера. Конфигурация должна быть установлена до sqlite3_initialize, из-за чего библиотека собирается с SQLITE_OMIT_AUTOINIT, а каждый вызывающий код проходит через один экспортированный initializer.

Не подключены по документированным причинам: Monocypher вообще не выделяет память — каждым буфером владеет вызывающий код. LibreSSL экспортирует CRYPTO_set_mem_functions, но его реализация представляет собой неработающий return 0;, поскольку custom allocators были удалены upstream. Вызов создавал бы ложное впечатление покрытия. ogg / vorbis / theora не предоставляют allocator hook.

При добавлении или обновлении vendored-библиотеки проверьте наличие allocator hook, подключите его либо запишите причину отказа. Читайте реализацию hook, а не только declaration: несколько интеграций в этой таблице первоначально были неверно поняты по call site или имени symbol.

Векторные контейнеры и inline storage

Containers.h предоставляет два sequence aliases с SafeAllocator<T>:

  • vector<T> является обычной динамически выделяемой последовательностью и остаётся default для неограниченных данных, persistent collections с амортизируемым allocation, move-heavy pipelines и точных интерфейсов движка.
  • small_vector<T, InlineCapacity> хранит до InlineCapacity элементов внутри объекта и переходит на storage с SafeAllocator при превышении лимита. Alias движка требует явную capacity; GCH_SMALL_VECTOR_DEFAULT_SIZE настраивает vendored implementation, но не задаёт политику выбора capacity.

Используйте small_vector только тогда, когда измерения или жёсткий protocol limit доказывают, что часто создаваемая коллекция обычно мала. Выбирайте capacity по наблюдаемой типичной cardinality, сохраняйте корректность редких больших случаев через heap spill и учитывайте inline bytes в каждом экземпляре. Scratch list на один вызов может быть хорошим кандидатом; несколько inline buffers в каждой ячейке плотной карты способны потребить больше памяти, чем сэкономит отсутствие первой allocation. Метод inlined() показывает текущий storage mode и полезен в focused tests и profiling instrumentation.

У представления есть несколько важных последствий для корректности:

  1. Перемещение inline small_vector переносит элементы во внутренний buffer destination-объекта. Указатели, references и iterators в source не следуют за перемещением так, как это обычно происходит при передаче heap allocation обычным vector. Проверьте каждый адрес, живущий дольше move.
  2. Inline moves и swaps выполняют операции над элементами и являются noexcept только условно. Заново определите гарантию exception safety затронутой функции, не наследуйте предположения от vector.
  3. Member small_vector<T, N> инстанцирует уничтожение inline elements на границе содержащего class. Тип T должен быть полным в этой точке; это не drop-in замена member vector, элемент которого только forward-declared.
  4. В vendored implementation member с вложенным element type, имеющим default member initializers, способен сделать default-inserting операции ill-formed, пока внешний class неполон, особенно под Clang. Для сокращения используйте erase(begin() + new_size, end()); в остальных случаях вынесите element type из внешнего class либо явно задайте требования к конструированию.
  5. Heap spill сохраняет terminate-on-OOM policy движка, потому что alias использует SafeAllocator. Конструирование, преобразование и move элементов всё ещё могут бросать исключения; см. Безопасность исключений.

Не заменяйте vector на small_vector через границу точного типа только потому, что набор операций выглядит одинаковым:

  • script export/codegen signatures и регистрация ScriptSystem используют точные написания и type identities vector<T> / readonly_vector<T>;
  • property writes, serialized backing stores, DataReader / DataWriter, NetBuffer и CScriptArray на отдельных границах имеют точный контракт vector;
  • внутренний helper, принимающий span, может обслуживать оба представления без раскрытия concrete container type, и это предпочтительная граница, когда допустимы оба;
  • FO_ENTITY_PROPERTY не может непосредственно принять small_vector<T, N>, потому что запятая одновременно разделяет аргументы макроса.

vector_collection допускает оба aliases движка для generic readers. Производящие helpers vec_filter, vec_transform и vec_sorted сохраняют различие vector / small_vector и inline capacity через rebind_vector_t; диапазоны других видов материализуются как vector. to_vector намеренно всегда создаёт vector, а copy_hold_ref предоставляет непрозрачный ref-held snapshot вместо concrete sequence contract. Generic formatter принимает оба aliases для обычных числовых элементов, но специальные случаи строк и bool сейчас совпадают только с точными типами vector<string> и vector<bool>. Не предполагайте такое же форматирование small_vector<string, N> или small_vector<bool, N> без расширения и тестирования formatter.

При внедрении запишите измеренное распределение и число объектов, проверьте lifetime адресов и места move/swap, подтвердите complete-type и exact-interface constraints, добавьте focused coverage для inline operation, spill и result type generic helpers. Затем запустите полный набор native unit tests, проектные audits exception safety и smart pointers, если они существуют, а также репрезентативные bake, gameplay и profiling paths изменённой подсистемы.

Контейнеры deque

DequeObject.* владеет basic_deque<T, BlockBytes>, который Containers.h экспортирует как deque<T, BlockBytes = DEQUE_BLOCK_BYTES>. Блок по умолчанию хранит 512 байт элементов и не меньше четырёх элементов; место использования может выбрать другой размер блока при наличии измерений. Рост с любого конца не перемещает существующие элементы и сохраняет стабильность ссылок, на которую опираются очереди сообщений, пакетов, задач, ввода и database commit. Не заменяйте его на std::deque: фиксированная политика блоков стандартной реализации не входит в allocator/performance-контракт движка.

Генерация случайных чисел

RandomGenerator.* владеет random_generator, источником xoshiro256++ движка. Конструктор по умолчанию получает seed от ОС, явный seed разворачивается через SplitMix64. Используйте next() для сырых битов, next_below(bound) для [0, bound), next_between(min, max) для включительного знакового диапазона и next_normalized() для [0, 1). Эти преобразования принадлежат движку и дают одинаковую последовательность на поддерживаемых стандартных библиотеках. Для поведения движка не используйте std::mt19937 и std::uniform_int_distribution.

Криптография

Cryptography.* владеет примитивами crypto:: для защищённого сетевого канала: X25519, BLAKE2b-512 и HMAC-BLAKE2b, ChaCha20-Poly1305 по RFC 8439. Одна и та же vendored Monocypher используется на native, Web и Android. crypto::fill_random получает ключевой материал от ОС через platform::fill_system_random: BCryptGenRandom на Windows, getentropy на Linux/Web, arc4random_buf на Apple и Android; при отказе ОС выбрасывается исключение. random_generator не подходит для создания ключа канала. Для каждого AEAD-сообщения создаётся новый контекст Monocypher: его потоковый контекст меняет ключ после первого сообщения. crypto::is_equal сравнивает за постоянное время, crypto::wipe стирает секретные буферы; владельцы обычных массивов ключей стирают их при завершении жизни.

Сериализация, значения, строки и хеши

StringObject.* владеет реализацией engine basic_string. API следует std::basic_string, а FO_STRING_INLINE_CAPACITY выбирает compiled small-string buffer для string и wstring; это build-wide ABI choice, а не per-call optimization. На трёх границах standard library нужны явные adapters: текст для standard stream копируется через make_stream_string, std::filesystem::path строится через fs_make_path, а getline вызывается без квалификатора, чтобы ADL выбрал overload движка. Рост строки следует тому же детерминированному terminate-on-OOM contract, что и остальные engine storage.

DataSerialization.* содержит binary read/write helpers, используемые сетью, persistence, ресурсами и тестами. DataReader::Read<T>() и DataWriter::Write<T>() копируют standard-layout values через byte copies, поэтому serialized streams не зависят от выравнивания buffer. Zero-copy overload ReadPtr<T>(size) предназначен только для raw byte/string views (uint8_t, char или void); типизированные значения, которым нужно alignment, должны использовать Read<T>() или ReadPtr(destination, size).

StringUtils.*, HashedString.*, StrongType.*, ExtendedTypes.*, SafeArithmetics.* и TimeRelated.* предоставляют небольшие переиспользуемые значения, которые вышележащие слои считают примитивами. iround отвергает non-finite и выходящие за диапазон int64 floating-point values до округления, чтобы ни одно значение с неопределённым для std::llround поведением не достигло функции. HashStorage::SetResolveHashFailureHandler позволяет вышележащему слою наблюдать неудачное разрешение hash как в throwing, так и во flagged no-throw lookup path, не обучая Essentials конкретной recovery policy.

Файловая система, сжатие, сокеты и рабочие потоки

DiskFileSystem.* владеет низкоуровневым доступом к диску. fs::disk_read_file удерживает descriptor и длину, read_at делает ограниченное позиционное чтение; region constructor открывает несжатый .fores внутри APK без копирования. fs::disk_write_file исключает конкурирующих writers, дописывает или обрезает незафиксированный хвост и flush-ит через fsync/_commit. fs::disk_directory_lock сериализует изменение ресурсов без lock file (flock в POSIX, named mutex в Windows); в отличие от mutex, повторный flock на тот же каталог не является reentrant даже в одном процессе. fs::rename_durable сохраняет rename, а fs::sync_parent — directory entry; fs::available_space помогает проверить место без preallocation возобновляемой загрузки. fs::is_contained_relative_path отвергает rooted path, .., colon, NUL и неверный UTF-8 до соединения с writable root. fs::make_writable_path помещает относительный path под user root. Для преобразования UTF-8 в std::filesystem::path используйте fs::make_path, обратно — fs::path_to_string: path.string() в Windows проходит через ANSI code page и может упасть на кириллическом профиле. Вышележащий mount описан в конфигурации и источниках данных. Compressor.* владеет сжатием, NetSockets.* — низкоуровневыми сокетами, WorkThread.* — фоновыми workers.

utf8::is_valid отвергает кодовые точки вне диапазона Unicode, суррогаты (U+D800–U+DFFF) и U+FFFD, зарезервированный как маркер ошибки декодера. strvex::is_valid_utf8 (доступный и через strex) декодирует всю последовательность байтов и применяет ту же проверку, отвергая также повреждённые, overlong и усечённые кодировки. Корректная кодировка UTF-8 символа U+FFFD по-прежнему отвергается. Встроенный NUL остаётся допустимым текстом; файловые пути отвергают его отдельно.

Каждая точка входа fs, принимающая путь, отвергает встроенный NUL до преобразования, извлечения родителя или I/O: ОС не должна обращаться к усечённому префиксу. Строковые пути на каждой платформе обязаны содержать корректный UTF-8; не-UTF-8 POSIX-имена отвергаются, а буквальные обратные слеши POSIX сохраняются. Преобразование native path в строку также проверяет результат, поэтому некорректное имя, найденное при обходе, не может попасть в возвращённые пути. Чистые преобразования (make_path, path_to_string, resolve_path, make_io_path, make_writable_path) сообщают неверный путь через std::filesystem::filesystem_error; make_io_path передаёт ошибку преобразования, а не игнорирует error code. Остальные операции используют прежний результат отказа или закрытый handle. iterate_dir бросает до вызова visitor для неверного пути. Отказ open_ifstream возвращает закрытый поток с failbit, поэтому hash не принимает ошибку преобразования за пустой файл. Чистая композиция writable path передаёт ошибку преобразования/выделения памяти, а не трактует неудачную классификацию как относительный путь.

Все файловые операции с результатом имеют noexcept: запросы, полное и ограниченное чтение, сравнение, hash, атомарная и эксклюзивная запись, удаление, rename/sync, блокировки и listing имён. Отказ передаётся прежним результатом: false, пустым optional, нулём, закрытым handle или накопленным списком имён. Ошибки выделения памяти/преобразования стандартных path, собственных streams и iterators обрабатываются на конкретной границе. Известные исключения сохраняют конкретный код ошибки; любое другое C++-исключение на этих границах возвращает io_error или прежний результат отказа. Запросам с готовым native path и операциям дескрипторов обёртка исключений не нужна. Общих catch вокруг операции и универсального noexcept-wrapper нет. Engine allocation сохраняет terminate-on-OOM policy.

Преобразования путей (make_path, path_to_string, resolve_path, make_io_path, make_writable_path) и open_ifstream, возвращающий стандартный поток по значению, могут бросать. iterate_dir передаёт исключения filesystem и visitor, четыре helper stream_* — исключения потока вызывающего кода. Эти интерфейсы не должны молча подменять исключение вызывающего кода отказом filesystem.

Полное чтение отвергает размер выше заданного ограничения или лимита engine string до выделения буфера и требует ровно предварительно полученное количество байтов. Hash читает ограниченные chunks до EOF, включая последний неполный chunk. Собственный поток создаётся на месте, чтобы потенциально бросающее перемещение не пересекло noexcept-границу. Listing сохраняет накопленные имена, если дальнейший обход остановлен OS-ошибкой, выделением памяти или преобразованием; ошибка старта возвращает пустой список. Неудачная запись может оставить родительские каталоги; cleanup пытается удалить только файл своей операции. Семантика пустого пути сохранена, включая create_directories(""), is_relative_path("") и блокировку текущего каталога. Извлечение native-родителя сохраняет POSIX-компоненты с обратным слешем.

fs::write_file_durable_new(path, bytes) эксклюзивно создаёт новый файл, записывает байты и синхронизирует descriptor перед закрытием. Существующее имя, включая каталог, даёт отказ без замены. При ошибке подготовки/записи/flush cleanup закрывает handle и пытается удалить только созданный этой операцией файл; ОС может отказать в самом удалении. Пустые байты допустимы, пустые и содержащие NUL пути отклоняются. Недостающие родительские каталоги создаются через native path adapter. Новое имя видно во время записи: для публикации готового файла потребитель пишет приватный stage, затем использует fs::replace_file. Синхронизация содержимого файла не синхронизирует родительский каталог и не обещает multi-file/power-loss транзакцию.

fs::disk_write_mode::create_new эксклюзивно создаёт файл и отказывает без обрезания, если путь существует. fs::write_file создаёт так временный файл рядом с назначением, сбрасывает и закрывает его, затем заменяет назначение; прежние читатели сохраняют descriptor и байты. Ошибка записи или публикации удаляет временный файл и сохраняет назначение. Helper не вызывает fs::sync_parent, поэтому атомарная видимость не обещает directory durability при отключении питания.

Обе перегрузки fs::write_file, строковая и байтовая, всегда публикуют готовый файл атомарно. Строковая перегрузка сохраняет NUL внутри строки; пустой ввод публикует пустой файл. Атомарной замене нужен доступ на запись в родительский каталог; она создаёт новый файловый объект и не сохраняет цель симлинка, идентичность hard link или индивидуальные метаданные назначения. Потребители, которым явно нужна идентичность файла, используют низкоуровневый поток fs::disk_write_file. Атомарная публикация не является транзакцией нескольких файлов или блокировкой между писателями.

Стадии соединяются через нативный файловый адаптер, а не strex::combine_path: ведущие пробелы, POSIX backslash и смысл родителя нельзя обрезать или нормализовать как resource paths. Успешная публикация освобождает cleanup-владение до повторного использования временного имени.

Windows-публикация зависит от возможностей ОС/файловой системы и sharing открытых handles. fs::disk_read_file разрешает delete sharing; CRT stream без него может блокировать замену. Native loaded DLL images также могут отказать на современном host. Тогда false сохраняет прежнее полное назначение и удаляет стадию; при необходимости закройте потребителей перед повтором. Атомарность не обещает заменяемость любого открытого файла. Нативные тесты проверяют отказ, прежние байты и успешный повтор после освобождения потребителей.

Windows-путь замены при открытом читателе резервирует завершающий UTF-16 NUL для FILE_RENAME_INFO; FileNameLength учитывает только байты имени. Тесты меняют выравнивание имени назначения, сохраняя прежнего читателя и проверяя полное новое содержимое отдельным чтением.

В Windows fs::make_io_path передаёт standard-library filesystem/file operations буквальный extended-length path, не меняя логический resource path. Дисковые тесты проверяют Unicode names длиннее 320 native characters и поведение trailing names. Применяйте преобразование на native I/O boundary, а не сокращайте project paths.

BuildTools/probe_windows_file_io.py отдельно проверяет канонический адаптер пути и тела directory/open/rename/remove операций со статическим и динамическим MSVC runtime. Он извлекает канонические зависимости проверки/преобразования пути и UTF-8 decoder; минимальный borrow shim реализует операции указателя decoder, поэтому engine pointer checks и allocator проверяются linked tests. Запись fixtures стандартным потоком проверяет этот адаптер; production-реализация атомарного fs::write_file проверяется нативными DiskFileSystem tests с полной линковкой.

Threading.h экспортирует coarse_sleep и precise_sleep; код движка не использует std::this_thread::sleep_for. coarse_sleep паркует поток без расхода CPU и предназначен для polling и других приблизительных ожиданий. precise_sleep использует high-resolution timer и вращает последний короткий интервал, поэтому предназначен для осознанных sub-millisecond deadlines — например, synchronization back-off и frame pacing. Обе функции noexcept; выбирайте их по требованиям к latency, а не заменяйте механически.

Последний интервал активно проверяет steady clock, не отдавая квант времени планировщику ОС: иначе другой готовый поток может занять процессор на целый квант. Активное ожидание ограничено последней миллисекундой запрошенного интервала. Вытеснение потока ОС всё равно может задержать возврат обеих функций, поэтому ни одна из них не гарантирует жёсткое реальное время.

work_thread::clear() подтверждается рабочим потоком под блокировкой состояния: очередь заданий очищается, уже выполняющееся задание завершается до подтверждения, а clear() возвращается после обработки запроса. Подтверждение пробуждает ожидающего при любой очереди — пустой или содержащей отложенные задания — и при переходах pause/resume. Сброс флага без уведомления мог оставить вызывающий поток в ожидании после того, как рабочий поток уже продолжил выполнение.

Когда job WorkThread бросает исключение, отчётом владеет зарегистрированный exception handler: он знает, что этот отказ значит для его владельца и когда об этом сообщить, и он же обновляет worker-owned policy, например очищает очередь jobs. Поток передаёт исключение global non-fatal exception reporter только тогда, когда handler не зарегистрирован: отчёт в обоих местах повторил бы исключение позади всего, что handler уже запустил.

Интеграция сборки

BuildTools/cmake/stages/EngineSources.cmake перечисляет в FO_ESSENTIALS_SOURCE каждую authored пару .h / .cpp из Essentials, два файла .inc и debugger visualization. Затем BuildTools/cmake/stages/CoreLibs.cmake создаёт из этого списка EssentialsLib. Библиотека входит в core dependency chain приложений, tools, tests и consumers generated code. При добавлении файла Essentials поместите его в правильную точку зависимостей Essentials.h, включите в FO_ESSENTIALS_SOURCE и добавьте focused coverage, где это возможно.

Какие тесты проверять

Прямое покрытие слоя Essentials находится в следующих тестах:

  • Source/Tests/Test_BaseLogging.cpp
  • Source/Tests/Test_BasicCore.cpp
  • Source/Tests/Test_CommonHelpers.cpp
  • Source/Tests/Test_Compressor.cpp
  • Source/Tests/Test_Containers.cpp
  • Source/Tests/Test_Cryptography.cpp
  • Source/Tests/Test_DequeObject.cpp
  • Source/Tests/Test_DataSerialization.cpp
  • Source/Tests/Test_DiskFileSystem.cpp
  • Source/Tests/Test_ExceptionHandling.cpp
  • Source/Tests/Test_ExtendedTypes.cpp
  • Source/Tests/Test_FunctionObjects.cpp
  • Source/Tests/Test_GenericUtils.cpp
  • Source/Tests/Test_GlobalData.cpp
  • Source/Tests/Test_HashedString.cpp
  • Source/Tests/Test_Logging.cpp
  • Source/Tests/Test_MemorySystem.cpp
  • Source/Tests/Test_NetSockets.cpp
  • Source/Tests/Test_Platform.cpp
  • Source/Tests/Test_RandomGenerator.cpp
  • Source/Tests/Test_SafeArithmetics.cpp
  • Source/Tests/Test_SmartPointers.cpp
  • Source/Tests/Test_StackTrace.cpp
  • Source/Tests/Test_StringObject.cpp
  • Source/Tests/Test_StringUtils.cpp
  • Source/Tests/Test_StrongType.cpp
  • Source/Tests/Test_TimeRelated.cpp
  • Source/Tests/Test_Threading.cpp
  • Source/Tests/Test_WorkThread.cpp

Test_Containers.cpp фиксирует alias движка, allocator, переход inline-to-heap, move, swap и форматирование. Test_DequeObject.cpp покрывает рост блоков, изменения с обоих концов, итераторы, стабильность ссылок, copy/move и разрушение. Test_RandomGenerator.cpp фиксирует seeded cross-platform sequence и bounded ranges. Test_Threading.cpp проверяет sleep primitives и их sub-millisecond поведение. Test_CommonHelpers.cpp фиксирует сохранение вида контейнера через rebind_vector_t и производящие helpers vec_*.

Полная карта suites и target wiring находится в разделе Тестирование.

Маршрутизация изменений

  • Ограничения компилятора/ОС, namespace, базовые aliases и низкоуровневые макросы: Source/Essentials/BasicCore.*.
  • Регистрация global create/delete callbacks: Source/Essentials/GlobalData.*.
  • Stack traces, журналирование и отчёты об исключениях: Source/Essentials/StackTrace.*, BaseLogging.*, Logging.*, ExceptionHandling.* и Native-, AngelScript- и Managed-отладка.
  • Общие средства памяти и указателей: Source/Essentials/MemorySystem.*, SmartPointers.* и Умные указатели.
  • Владение callable и inline targets: Source/Essentials/FunctionObjects.*.
  • Строки движка и build-wide inline-capacity contract: Source/Essentials/StringObject.*; хранение deque: DequeObject.*; aliases и stream interop: Containers.h.
  • Изоляция OS-вызовов и dispatch: Source/Essentials/WinApi.*, Posix.* и Platform.*.
  • Cross-platform random sequences: Source/Essentials/RandomGenerator.*; coarse/precise waits: Threading.*.
  • Байты файлов и низкоуровневая сборка writable path на диске: Source/Essentials/DiskFileSystem.*; смонтированные ресурсы движка и overlays установленного клиента: Конфигурация и источники данных.
  • Socket primitives: Source/Essentials/NetSockets.*; protocol, command и network runtime: Сеть.

fs::try_lock_file(path) возвращает перемещаемый fs::exclusive_file_lock с owns_lock() и идемпотентным reset(). Открывает или создаёт файл без усечения, не ожидает и возвращает незахваченного владельца для занятых, некорректных или недоступных путей и в Web. Родительские каталоги уже должны существовать. Windows использует wide paths, нормализацию длинных путей и запрет совместного открытия; POSIX — advisory exclusive flock. Перемещение передаёт владение, присваивание освобождает прежний lock, деструктор и выход процесса освобождают захват. Дескрипторы не наследуются при запуске дочерних исполняемых программ. Файл остаётся на диске; не удаляйте его, пока участник может удерживать захват. Владение Baker описано в руководстве по запеканию.

Контрольный список проверки

  1. Убедитесь, что изменение не вводит зависимость Essentials от вышележащего слоя движка.
  2. При добавлении или удалении файлов Essentials обновите BuildTools/cmake/stages/EngineSources.cmake.
  3. Запустите минимальный подходящий Essentials test, затем более широкий target RunUnitTests, если изменение пересекает границы утилит.
  4. Для диагностики также проверьте актуальность раздела Native-, AngelScript- и Managed-отладка.
  5. Для файловой системы, сокетов или threading проверьте хотя бы одного вышележащего consumer, если низкоуровневый контракт изменился.
  6. При внедрении small_vector подтвердите capacity и object-count измерениями, проверьте move/address lifetime и exact-type boundaries, затем повторно запустите gates exception safety и pointer ownership.
Введите запрос.