Базовый слой 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.hSource/Essentials/Essentials.cppSource/Essentials/BasicCore.hSource/Essentials/BasicCore.cppSource/Essentials/GlobalData.hSource/Essentials/GlobalData.cppSource/Essentials/StackTrace.hSource/Essentials/StackTrace.cppSource/Essentials/BaseLogging.hSource/Essentials/BaseLogging.cppSource/Essentials/FatalError.hSource/Essentials/FatalError.cppSource/Essentials/FunctionObjects.hSource/Essentials/FunctionObjects.cppSource/Essentials/SmartPointers.hSource/Essentials/SmartPointers.cppSource/Essentials/MemorySystem.hSource/Essentials/MemorySystem.cppSource/Essentials/StringObject.hSource/Essentials/StringObject.cppSource/Essentials/DequeObject.hSource/Essentials/DequeObject.cppSource/Essentials/Containers.hSource/Essentials/Containers.cppThirdParty/small_vector/README.mdThirdParty/small_vector/source/include/gch/small_vector.hppSource/Essentials/StringUtils.hSource/Essentials/StringUtils.cppSource/Essentials/WinApi.hSource/Essentials/WinApi.cppSource/Essentials/Posix.hSource/Essentials/Posix.cppSource/Essentials/Platform.hSource/Essentials/Platform.cppSource/Essentials/ExceptionHandling.hSource/Essentials/ExceptionHandling.cppSource/Essentials/RandomGenerator.hSource/Essentials/RandomGenerator.cppSource/Essentials/Cryptography.hSource/Essentials/Cryptography.cppThirdParty/Monocypher/src/monocypher.hThirdParty/Monocypher/src/monocypher.cSource/Essentials/Threading.hSource/Essentials/Threading.cppSource/Essentials/SafeArithmetics.hSource/Essentials/SafeArithmetics.cppSource/Essentials/DataSerialization.hSource/Essentials/DataSerialization.cppSource/Essentials/HashedString.hSource/Essentials/HashedString.cppSource/Essentials/StrongType.hSource/Essentials/StrongType.cppSource/Essentials/TimeRelated.hSource/Essentials/TimeRelated.cppSource/Essentials/ExtendedTypes.hSource/Essentials/ExtendedTypes.cppSource/Essentials/Compressor.hSource/Essentials/Compressor.cppSource/Essentials/WorkThread.hSource/Essentials/WorkThread.cppSource/Essentials/Logging.hSource/Essentials/Logging.cppSource/Essentials/DiskFileSystem.hSource/Essentials/DiskFileSystem.cppSource/Essentials/CommonHelpers.hSource/Essentials/CommonHelpers.cppSource/Essentials/NetSockets.hSource/Essentials/NetSockets.cppSource/Essentials/UcsTables.incSource/Essentials/WinApiUndef.incBuildTools/natvis/essentials.natvisBuildTools/cmake/stages/EngineSources.cmakeBuildTools/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.
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"); из BasicCore.h помечает необходимый код, распознающий старые сборки или данные, в том числе путь отказа. Его static_assert проверяет форму даты и допустим в namespace, классе или блоке; сама сборка по календарю не перестаёт работать. Для managed-кода есть [TemporaryCompat("Id", "YYYY-MM-DD")] из CoreScripts/Attributes.cs. Во всех связанных реализациях, полях и тестах повторяются один идентификатор и одна дата — последний день действия.
BuildTools/temporary_compat.py по умолчанию сканирует Engine Source/, отвергает неверно записанные маркеры, несовпадающие даты и срок более 366 дней; со следующего после истечения дня проверка падает с перечнем всех мест. Workflow проверки движка запускает тесты и сканер. Подключающий проект может передать свои каталоги исходников вместе с Engine/Source. Маркер не заменяет обоснования сохранённой совместимости. По истечении срока код удаляют либо дату переносят отдельным проверенным изменением, если причина ещё сохраняется. Срок проверяется в CI, а не во время компиляции, чтобы старую сборку можно было воспроизвести.
Диагностика и обработка сбоев
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используют enginebasic_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.cpp через png_create_read_struct_2 |
| libbson / mongo-c | safe_alloc::*_raw + aligned |
общий Server/DataBase.cpp; каждая BSON-backed factory для JSON, SQLite и 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, поэтому каждая BSON-backed factory устанавливает одинаковые 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.
У представления есть несколько важных последствий для корректности:
- Перемещение inline
small_vectorпереносит элементы во внутренний buffer destination-объекта. Указатели, references и iterators в source не следуют за перемещением так, как это обычно происходит при передаче heap allocation обычнымvector. Проверьте каждый адрес, живущий дольше move. - Inline moves и swaps выполняют операции над элементами и являются
noexceptтолько условно. Заново определите гарантию exception safety затронутой функции, не наследуйте предположения отvector. - Member
small_vector<T, N>инстанцирует уничтожение inline elements на границе содержащего class. ТипTдолжен быть полным в этой точке; это не drop-in замена membervector, элемент которого только forward-declared. - В vendored implementation member с вложенным element type, имеющим default member initializers, способен сделать default-inserting операции ill-formed, пока внешний class неполон, особенно под Clang. Для сокращения используйте
erase(begin() + new_size, end()); в остальных случаях вынесите element type из внешнего class либо явно задайте требования к конструированию. - Heap spill сохраняет terminate-on-OOM policy движка, потому что alias использует
SafeAllocator. Конструирование, преобразование и move элементов всё ещё могут бросать исключения; см. Безопасность исключений.
Не заменяйте vector на small_vector через границу точного типа только потому, что набор операций выглядит одинаковым:
- script export/codegen signatures и регистрация
ScriptSystemиспользуют точные написания и type identitiesvector<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.
В 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.
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, а не заменяйте механически.
Когда 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.cppSource/Tests/Test_BasicCore.cppSource/Tests/Test_CommonHelpers.cppSource/Tests/Test_Compressor.cppSource/Tests/Test_Containers.cppSource/Tests/Test_Cryptography.cppSource/Tests/Test_DequeObject.cppSource/Tests/Test_DataSerialization.cppSource/Tests/Test_DiskFileSystem.cppSource/Tests/Test_ExceptionHandling.cppSource/Tests/Test_ExtendedTypes.cppSource/Tests/Test_FunctionObjects.cppSource/Tests/Test_GenericUtils.cppSource/Tests/Test_GlobalData.cppSource/Tests/Test_HashedString.cppSource/Tests/Test_Logging.cppSource/Tests/Test_MemorySystem.cppSource/Tests/Test_NetSockets.cppSource/Tests/Test_Platform.cppSource/Tests/Test_RandomGenerator.cppSource/Tests/Test_SafeArithmetics.cppSource/Tests/Test_SmartPointers.cppSource/Tests/Test_StackTrace.cppSource/Tests/Test_StringObject.cppSource/Tests/Test_StringUtils.cppSource/Tests/Test_StrongType.cppSource/Tests/Test_TimeRelated.cppSource/Tests/Test_Threading.cppSource/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: Сеть.
Контрольный список проверки
- Убедитесь, что изменение не вводит зависимость Essentials от вышележащего слоя движка.
- При добавлении или удалении файлов Essentials обновите
BuildTools/cmake/stages/EngineSources.cmake. - Запустите минимальный подходящий Essentials test, затем более широкий target
RunUnitTests, если изменение пересекает границы утилит. - Для диагностики также проверьте актуальность раздела Native-, AngelScript- и Managed-отладка.
- Для файловой системы, сокетов или threading проверьте хотя бы одного вышележащего consumer, если низкоуровневый контракт изменился.
- При внедрении
small_vectorподтвердите capacity и object-count измерениями, проверьте move/address lifetime и exact-type boundaries, затем повторно запустите gates exception safety и pointer ownership.