Skip to content

Version 2.1.0

Latest

Choose a tag to compare

@StormBytePP StormBytePP released this 19 Oct 20:15

[Summary]

StormByte Base is the C++26 foundation of the StormByte suite.

Every other module links this library.
This repository is not Buffer, Config, Crypto, Database, Logger, Multimedia, Network or System — those live in their own repos and depend on Base.

Public headers under StormByte/ cover exceptions, Expected, little-endian Serializable, strings, paths, UUID v4, bitmasks, clonable types, ThreadLock, Size, and StormByte::Type concepts.
StormByte::Safe is the owned surface that crosses a DLL without the caller's CRT: collections, wait, and Safe::Thread.

If you landed here from a release link and have not read the tree:

  • What this module is, how to build it, and short examples: README.md
  • Since 2.0.0 license changed: original source in this repository is dual-licensed, LGPL v3 or later or a commercial license from the copyright holder. The grant does not cover other StormByte modules or thirdparty/. LICENSE

Added

  • Safe::String::Implode and Safe::WString::Implode join a Vector or a Queue of text with a string_view / wstring_view delimiter. The containers are taken by const reference. The result size is counted first and reserved once, so a short join stays in SSO.
  • std::numeric_limits<Size> and std::numeric_limits<ByteSize> are specialized. min() is 0 and max() is 2^64 - 1, the stored uint64_t range, not the host std::size_t range.
  • Safe::String::ReplaceToken and Safe::WString::ReplaceToken are static. There is no member and no in-place overload. The call is String::ReplaceToken(text, args...) / WString::ReplaceToken. text is a view, so a literal converts. One argument replaces the first literal {}; further arguments replace the following tokens in order. A missing token leaves the text unchanged. A view is used as-is and does not allocate. A number uses std::to_string / std::to_wstring. A char or wchar_t is that one character. Anything else is accepted when it converts to std::string / std::wstring or to Safe::String / Safe::WString; an explicit conversion is enough, so Size and ByteSize need no cast. A result that fits SSO is written into the inline array and does not call SDS / wbuf. A longer result is one exact Base allocation.
  • ENABLE_LTO, default ON. Link-time optimization follows this option for every build type, not Release. It combines with ENABLE_PGO.
  • ENABLE_PGO. The build directory stays a normal build and is the one cmake --install ships. Training is a separate tree, <build>/pgo-instr: instrumented library, tests and ctest. This directory then compiles with that profile and without tests. install() rules are unchanged. The steps are in cmake/pgo.cmake. The instrumented configure receives every project cache option (WITH_GPL, ENABLE_LTO, …) and prints each one. It uses the same C, C++ and linker flags as the final build, including -stdlib= and, on MSVC or clang-cl, /arch:AVX2 and /DNDEBUG. On clang-cl the profile is applied at compile time. The instrumented link uses clang_rt.profile (under lib/windows in the Windows LLVM layout) instead of a flag passed to lld-link. GCC names the .gcda files with -fprofile-prefix-path so pgo-instr is not part of the path the final build looks up. On Windows, ctest prepends <build>/pgo-instr/lib to PATH.
  • Release archives include THIRD-PARTY (not THIRDPARTY: on Windows and macOS that name is the same path as the thirdparty/ directory, so the file never landed in those zips) and COPYING.SDS. SDS commit cc59ce0b12e91521476ad54a1e069fb6fd933bbc is compiled into the shared library. COPYING.SDS reproduces the notice in those sources and the shorter LICENSE from the same commit. LICENSE in this repository still covers only original StormByte source.

Changed

  • CI builds with ENABLE_LTO=ON, ENABLE_PGO=OFF and ENABLE_TEST=ON, then runs ctest. Profile-guided optimization stays on the release workflow.
  • Breaking:
    • Container size_type is Size. Vector, Deque, List, Queue, Map, Set, UnorderedMap, UnorderedSet, Iterable and Binary no longer use std::size_t as size_type. Counts convert implicitly to std::size_t when indexing memory. UnorderedMap keeps its power-of-two mask on std::size_t; Size has no bitwise operators.
    • Exception. Public constructors no longer take std::format_string. Exception, Safe::Exception, DeserializeError, OperationError, Base64Error, ExpiredWeakPointerError and BadVariantAccess take std::string_view. A Safe::String converts to that view and is copied; there is no move constructor. A derived class formats through a protected constructor that calls Safe::String::ReplaceToken, so it accepts the same fields as that function. A view is used as-is. A number, or anything that converts to a string, is converted inside the template. A throw site that is not a derived constructor builds the body with ReplaceToken and copies it in. Indexed arguments, fill characters and presentation types outside d, f, g, G, x and X are not fields.
    • Safe::OutOfBoundsError. Constructors no longer take a message. OutOfBoundsError(Size) writes index <n> is out of range. OutOfBoundsError() is the same text without a number, for a failure that has no numeric bound (a missing map key, an empty container).
  • PerformanceTests also trains Explode, Implode, ReplaceToken, Split, case conversion, in-place edit, in-range at and ByteSize text.
  • Safe::String SSO capacity is 32 code units (48-byte Storage, 16-byte aligned). Safe::WString is 11 units with 16-byte alignment: 32 units would make the object ~136 bytes on 64-bit and hurt cache density; 11 keeps it compact while covering typical short wide strings.
  • A public function that has to return a std:: object is STORMBYTE_FORCE_INLINE in the header. The caller builds it; the DLL does not. That is the same rule as operator std::string. It now also covers Safe::Binary::span and its std::span conversion, Error::make_error_code, Clock::Stop, Clock::Time, Clock::MeanDuration and Clock::GetValues (the DLL returns a plain microsecond count), and the Codec reads of std::string, std::wstring, std::u16string and std::u32string. Those four are no longer explicit instantiations of Serializable. The DLL side of a string read returns Safe::String or Safe::WString. std::error_category::message stays on the category singleton: the standard virtual is bound to that object and cannot move into the caller.
  • ReplaceToken accepts the fields Multimedia still writes with std::format: {:.3f}, {:.6f}, {:.9f}, {:.17g}, {:6.2f}, {:02}, {:03} and {:016x}. A field is {, an optional colon, an optional 0, an optional width, an optional .precision, an optional type d / f / g / G / x / X, then }. Width pads with spaces, or with zeros when 0 is set, and the sign stays in front of the zeros. f is fixed and g / G is general, both through std::to_chars, so {:.3f} rounds the same way std::format does. A bare {} on a number is still std::to_string / std::to_wstring. A bool is true or false even when the field has a numeric spec. A character pointer (char* / wchar_t*, including const) is text, the same as a literal; a null one inserts nothing. Any other raw pointer is 0x plus lowercase hex of its address (0x0 when null). Safe::Unique and Safe::Shared print get(), the object they own, not the control block. {:016x} on a pointer, on Size or on ByteSize is the integer form, without the 0x prefix. A bare {} on Size is its decimal text. A bare {} on ByteSize is the IEC length (12 B, 1.50 KiB). A numeric field on either one formats the stored uint64_t. A brace run that is not one of these fields stays in the text and does not consume an argument. The replacement text is still built in the caller. Only the finished bytes cross into ReplaceTokens.

Fixed

  • FindStormByte accepts the Base component. That name is the installed core library, not libStormByte-Base. find_package(StormByte REQUIRED Base Logger System) no longer fails with missing: Base when StormByte is already installed. The imported targets are StormByte-Base and StormByte::Base.
  • -stdlib=libc++ and -stdlib=libstdc++ do not count as custom C++ flags. If either is the only extra flag, Release still gets -O2 and the CPU baseline.
  • Safe::Deque::back on an empty deque throws OutOfBoundsError. It used to evaluate size - 1 first, and Size asserts instead of wrapping when the count would go negative.
  • Safe::Thread starts a UNIX execution with an 8 MiB stack. std::thread left the size to the libc, and musl's 128 KiB default overflowed when libaom ran its first encoder worker on that execution. Windows is unchanged. ThreadTests walks a megabyte of stack on UNIX; a 128 KiB stack dies there.
  • operator<=> no longer crosses the DLL. std::strong_ordering is not the same ABI in clang-cl and MSVC: the clang build writes the result through a hidden pointer and MSVC does not pass one, so i < size wrote to address 0. Mixed Size / ByteSize comparisons are no longer extern template exports. Safe::Binary and every other three-way comparison are STORMBYTE_FORCE_INLINE in the header, so the caller compiles them.
  • MSVC PGO links with /LTCG:PGINSTRUMENT and /LTCG:PGOPTIMIZE. /GENPROFILE and /USEPROFILE alone fail with LNK1220 on the current link.exe, because they require an /LTCG specification. CMake's own /LTCG stays off so it does not replace the profile mode. The profile mode is add_link_options on this project, not CMAKE_EXE_LINKER_FLAGS. A nested project() inherits the cache and its compiler test links cmTC_*.exe, which was never instrumented, so /LTCG:PGOPTIMIZE aborted with LNK1266 (cmTC_*.pgd missing). The cache keeps a bare /LTCG so those /GL objects still link. A static dependency stays LTCG and is optimized by the link that owns the .pgd.
  • Clang PGO drops .profraw files left by compiler probes before ctest, and llvm-profdata merge uses -failure-mode=all. A probe writes a few hundred bytes for main, with a padded empty symbol name. The default failure mode is any, so that one file aborted the merge even after the instrumented tests had passed.