Skip to content

Version 2.0.0

Latest

Choose a tag to compare

@StormBytePP StormBytePP released this 01 Oct 20:05

[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.

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 / Safe::WString — UTF-8 and wide owned text integrated from StormByte-String into Base, with range, lookup, case conversion and serialization support. Views are copied when retained; Bytes() exposes a non-owning C pointer. Explicit construction from Safe::CString / Safe::WCString remains available, with StringTests / WStringTests.
  • Telemetry and Clock — base session telemetry (Telemetry) with pure operator Safe::String and a protected named clock drawer (Clock) backed by a thread-safe PIMPL store (Safe::Unique<Store>). Leaves derive and add their own domain metrics and counters; Clock provides a lightweight stopwatch measuring intervals, count, cumulative time, and mean duration. Covered by TelemetryTests.
  • STORMBYTE_PUBLIC_TYPE — in visibility.h. Default visibility on ELF / Mach-O, empty on Windows. Put on header-only types (templates included) whose typeinfo and vtable every module emits, so typeid / dynamic_cast agree across DLLs (on ELF the loader also merges the copies) even when a consumer builds with -fvisibility=hidden. Windows needs nothing: MSVC compares RTTI by name and implicitly exports the base specializations of an exported class.
  • STORMBYTE_FORCE_INLINE — in platform.h. inline plus __forceinline or always_inline, so the body is emitted in the caller. inline alone is only a hint.
  • BinaryData — owned contiguous sequence of std::byte, safe to use across a DLL boundary. Same kind of API as std::vector<std::byte> (iterators, <algorithm>, std::ranges, std::span, insert / erase / assign / append / emplace_back / operator+=). Lengths and indices use StormByte::ByteSize. Storage lives on Base's heap; the public type is not std::vector<std::byte>. at() throws OutOfBoundsError. Construct from span, pointer+ByteSize, range, initializer list, string_view, and a caller-owned std::vector<std::byte> (lvalue copies and leaves the vector; rvalue copies onto Base's heap then clears the vector — looks like a move, not a heap steal). Convert out with implicit span and explicit operator std::vector<std::byte> (const& copies and leaves *this; && copies onto the caller CRT then clears *this). append(BinaryData&&) / operator+=(BinaryData&&) are a real same-heap move when *this is empty. Compare equal / unequal / three-way with another BinaryData and with std::span<const std::byte> (both operand orders). HexDump() and HexDump(Size columns) return a Safe::String: 8-digit offset, hex row, ASCII (non-printable as .). columns is a row width, not a byte length. 0 prints every byte on one line. Default HexDump() is 16 columns. Type::Container / Type::Sized / Type::HasPushBack / Type::ByteInputRange match; Type::String does not. Serializable<BinaryData> uses the container path (same wire as std::vector<std::byte>: uint64 LE count + payload). Covered by BinaryDataTests and SerializationTests.
  • CString — owned NUL-terminated buffer, safe to use across a DLL boundary. Not a std::string. Copy, move, Reset, swap, Length (StormByte::Size), observer operator[] (indices [0, Length()]; Length() is the trailing NUL; null or past Length() is undefined and asserts when assertions are on), explicit operator bool (non-null pointer; "" is valid empty text), explicit const char* (same lifetime as std::string::c_str()), explicit std::string_view (empty view when null; [0, Length()); same lifetime), implicit std::string, operator<<, content == / != / <=>, and std::hash. Construct from const char* (null stays null), std::string_view and const std::string& (copy onto Base's heap, not a steal; empty yields "", not null). Covered by CStringTests.
  • WCString — wide counterpart of CString (wchar_t / std::wstring / std::wstring_view / std::wostream). Same comparison, hash, subscript and lifetime rules as std::wstring::c_str(). Length() is StormByte::Size. Construct from const wchar_t* (null stays null), std::wstring_view and const std::wstring& (copy onto Base's heap; empty yields L""). Covered by WCStringTests.
  • Error — Domain, Category, Code (Success, Unknown) and Fault. Modules specialize Domain for their enums; make_error_code lives next to the enum so ADL feeds std::error_code. Category singletons stay in the module .cxx. Fault holds the code and a Safe::String. Not thrown. Covered by ErrorTests.
  • Shared — StormByte::Safe::Shared, complements std::shared_ptr (StormByte/safe/pointers.hxx). It does not replace it. Exact type: Safe::Heap::MakeShared. Derived type: Shared<Base>::MakePointer<Derived> (the deleter still destroys the derived object). Daily operations match std::shared_ptr (copy, reset, swap, compare, use_count, owner_before). No constructor from a raw pointer or from std::shared_ptr, and no release. Converts implicitly to std::shared_ptr<T> (same control block, deleter stays Base). No conversion back. StaticPointerCast, DynamicPointerCast, ConstPointerCast and ReinterpretPointerCast keep that control block. Covered by SafePointersTests.
  • Unique — StormByte::Safe::Unique, complements std::unique_ptr (StormByte/safe/pointers.hxx). It does not replace it. Exact type: Safe::Heap::MakeUnique. Derived type: Unique<Base>::MakePointer<Derived>, and ~Base must be virtual or the call does not compile. No release and no constructor from a raw pointer. Converts on move to std::unique_ptr<T, Safe::Heap::ObjectDeleter> only. A std::unique_ptr<T> parameter has to change. Safe::Heap stays private and is not installed. Covered by SafePointersTests.
  • Weak — StormByte::Safe::Weak, complements std::weak_ptr. Constructed only from Shared. lock returns Shared, or empty when expired. No constructor from std::weak_ptr. Covered by SafePointersTests.
  • ClonableTests — clone / move / MakePointer coverage for Shared and Unique. Derived::PointerType used as storage. Implicit conversion to std::shared_ptr / unique_ptr with Base deleter. ValidSmartPointer rejects std::shared_ptr and default std::unique_ptr. A hidden-visibility shared library (ClonablePlugin) derives from Clonable; the tests check one shared typeid, dynamic_cast and Clone / Move across that boundary.
  • Size — abstract unit count (uint64_t storage), same width on every host and safe across a DLL. Not an octet length. Implicit conversion only to std::size_t (clamp to size_t::max); every other integral destination is explicit and clamps to T::max. No Value(), no operator bool. Full arithmetic with any Type::Integral and with another Size; the result is always Size. Mixed == / <=> with any integral and with ByteSize are hidden friends so n == 5 is not ambiguous. A negative integer operand is undefined and asserts when assertions are on. Overflow wraps in release. operator Safe::String / operator Safe::WString print the raw count (atoi style). Explicit instantiations of the templates live in the StormByte DLL. Covered by SizeTests.
  • ByteSize — octet length (uint64_t storage), same width on every host and safe across a DLL. Implicit conversion only to std::size_t (clamp); every other integral destination is explicit. No Value(), no operator bool. Area products (ByteSize * ByteSize) are deleted. Scaling by Size or by an integer is allowed and yields ByteSize. IEC factories and free constants (B, KiB, MiB, GiB, TiB, PiB, EiB) so 1 * KiB and ByteSize::KiB(1) are 1024 octets. SI (KB…EB) likewise. operator Safe::String / operator Safe::WString print IEC text (0 B, 1023 B, 1.00 KiB, 1.50 MiB). Explicit instantiations live in the StormByte DLL. Covered by ByteSizeTests.

Changed

  • Text API migration — CString / WCString move to StormByte/safe/ and remain public for explicit buffer use. Base text-producing APIs (Size, ByteSize, UUID, Base64, BinaryData, Telemetry) now expose Safe::String / Safe::WString. Error messages and exceptions store Safe::String; exception plain-text constructors accept std::string_view or const Safe::String& and copy the text. Both buffer and text-wrapper serialization retain the standard string wire format.
  • Shared vs static follows CMake BUILD_SHARED_LIBS. There is no STORMBYTE_SHARED CMake option. When the library is shared, the compile definition STORMBYTE_SHARED is still set so visibility.h can distinguish dllexport / dllimport / static.
  • License change — original source in this repository is dual-licensed: GNU LGPL v3 or later, or a commercial license from the copyright holder. The grant applies only to original StormByte source in this repository. It does not cover other StormByte modules or third-party material (including thirdparty/). No patent rights are granted.
  • Visibility — STORMBYTE_PUBLIC comes first on a function declaration (STORMBYTE_PUBLIC Safe::CString Foo();). clang-cl rejects __declspec after a reference return. class STORMBYTE_PUBLIC stays on the type. Exported templates are declared extern template STORMBYTE_PUBLIC / extern template class STORMBYTE_PUBLIC in the header and instantiated in the .cxx with STORMBYTE_INSTANTIATE (dllexport on Windows, empty on ELF so GCC does not warn -Wattributes). The extern line in the header is what ELF uses to export; do not put STORMBYTE_INSTANTIATE on that line.
  • Breaking: Exception. StormByte::Component is gone. what() is StormByte: <message>, or StormByte.<path>: <message> when a parent passes the segments under StormByte (Crypto.Crypter), joined with .. A parent passes Exception::Path (a std::string_view of its segments) and forwards the format and the arguments. A bare string is not a path, because that is ambiguous with the format constructor. It does not format. Exception calls std::format in the caller's translation unit and copies a const char* into a Safe::String. The view is not stored and no std::string enters the DLL. A final leaf inherits the parent constructors and adds no segment. A literal with no arguments is the message as-is. DeserializeError, OutOfBoundsError and Base64Error are leaves of the root (StormByte: …). Each of those types, and the root, defines its destructor in Base's .cxx so the typeinfo is unique across a DLL. Named types in other modules do the same.
  • Expected — the error is a Safe::Shared<E>, not a std::shared_ptr built with std::make_shared. Unexpected uses Safe::Heap::MakeShared, or Safe::Shared::MakePointer for a derived error. The call stays Unexpected<E>("… {}", args…). Formatting runs in the caller; E is constructed from the resulting string. Unexpected(result.error()) forwards that Shared and does not allocate. Shared<E> converts to std::shared_ptr<E>. A std::shared_ptr is not accepted. Expected<T, E> is still std::expected. Covered by ExpectedTests.
  • Breaking: Clonable — moves to StormByte::Safe::Clonable (StormByte/safe/clonable.hxx; StormByte/clonable.hxx is gone). Polymorphic Clone / Move. It is not an owner: MakePointer forwards to Shared::MakePointer or Unique::MakePointer, which allocate the object and the shared_ptr control block on Base's heap (Safe::Heap::Allocate / Safe::Heap::Free in a private TU that is not installed). Clonable<T> is Clonable<T, Shared<T>>. Unique ownership is Clonable<T, Unique<T>>. Clonable<T, std::shared_ptr<T>> and Clonable<T, std::unique_ptr<T>> no longer compile. PointerType is Shared<T> / Unique<T>; overrides that already return PointerType from MakePointer do not change. After construction, Shared converts implicitly to std::shared_ptr<T> so existing shared_ptr call sites keep working. Unique does not convert to std::unique_ptr<T>. ValidSmartPointer uses Type::SameAs. A class in another DLL may derive from Clonable: Clonable, Shared, Unique, Weak, Heap::ObjectDeleter and Heap::Allocator carry STORMBYTE_PUBLIC_TYPE, so their per-module typeinfo and vtables merge instead of staying hidden in each DLL (before, Clonable<T> was hidden in a -fvisibility=hidden consumer, and address-comparing runtimes such as libc++ disagreed on typeid / dynamic_cast). Covered by ClonableTests.
  • Breaking: StormByte::Safe — Shared, Unique, Weak, the pointer casts, Heap and Clonable / ValidSmartPointer live in StormByte::Safe, under StormByte/safe/ (pointers.hxx, clonable.hxx). StormByte/safe_pointers.hxx and StormByte/clonable.hxx are gone. No aliases are kept in StormByte.
  • Type::String — also matches StormByte::Safe::String and StormByte::Safe::WString. They stay out of Type::Container.
  • Type::Sized — size() may be implicitly convertible to std::size_t (STL containers) or be StormByte::Size / StormByte::ByteSize. Covered by TypeTraitsTests.
  • Type::Numeral — also matches StormByte::Size and StormByte::ByteSize.
  • Type::Array — StormByte flavor of a fixed-size sequence; Serializable uses it instead of stock std::is_same / tuple_size probes. BinaryData is not an array.
  • GenerateUUIDv4 — returns Safe::String instead of std::string.
  • Base64Encode — both overloads return Safe::String instead of std::string. Base64Decode returns BinaryData and still takes std::string_view.
  • Size vs ByteSize everywhere — public Base APIs no longer take or return a raw std::size_t / std::uint64_t when the value is a count. Character counts (Safe::CString::Length, Safe::WCString::Length, subscripts) are Size. Octet counts (BinaryData::size / operator[] / ctors / Serializable<T>::Size, wire lengths) are ByteSize. Mixed arithmetic and comparison stay typed: a Size result stays a Size; a ByteSize result stays a ByteSize.
  • Serializable — container and string codecs use ByteSize for the on-wire length. Concepts used to branch (Type::Array, Type::Container, Type::String, Type::Numeral) are StormByte flavor, not stock std::* traits.
  • Tests — suite section headers (// -------------------) match in the body and in main. UUID, Base64, Bitmask, Clonable, Exception, Expected, ThreadLock, TestHandlers, Iterable, Serialization, SafePointers, TypeTraits, Size, ByteSize and BinaryData cover the public surface (format, padding, invalid input, clone independence, non-owner unlock, bounds, wire, corruption, units, * / / / %, <algorithm> / ranges / iterators, HexDump columns, span comparison, operator+=, IEC text). Type::Sized covers a container whose size() returns StormByte::Size or StormByte::ByteSize. Base64Decode of an encode result uses an explicit std::string_view. SerializationTests cover BinaryData roundtrip, the same wire as std::vector<std::byte>, truncation, a huge size field, bit-flip / byte-overwrite / random corruption, trailing garbage and std::optional<BinaryData>.

Removed

  • Old root String helpers (StormByte::String in the former Base API) — replaced by the owned StormByte::Safe::String / WString types in Base.
  • System (StormByte::System in Base: TempFileName, CurrentPath, ExecutablePath, Sleep) — leaves Base. Absorbed by the existing StormByte-System module.
  • UTF8Error / SystemError — leave Base with String and System. Derived exceptions that remain are DeserializeError, OutOfBoundsError and Base64Error.
  • CoreApiTests — coverage lives in ClonableTests and ErrorTests.

Fixed

  • FindStormByte. String is no longer a package component; Base now provides the owned text types. Buffer pulls Logger and System; Database pulls Logger. Crypto, Multimedia and Network name only Buffer; the closure finds Logger and System. Config links only the core.
  • ByteSize / Size. The integer constructors stay in the header. GCC does not emit a constexpr constructor that is both an extern template and an explicit instantiation, so SizeTests crashed and ByteSize(unsigned long long) was missing from the shared library. The operators are still one copy in the DLL. ++ / -- build the step with the private constructor, so they do not instantiate unsigned int early.
  • Size / ByteSize to std::string. The conversion calls operator Safe::String() by name. On GCC, static_cast<Safe::String> picks Safe::String(std::string) and that calls the same operator again until the stack dies.
  • Safe::CString / Safe::WCString. Each constructs from the other. Narrow text is UTF-8. ByteSize's wide form uses that conversion. swprintf and %s is a narrow string on glibc and a wide string on the Windows CRT, so 1.00 KiB did not match.
  • Caller containers. Safe::CString::operator std::string, Safe::WCString::operator std::wstring, Size / ByteSize operator std::string, their operator<<, and BinaryData::operator std::vector are STORMBYTE_FORCE_INLINE. On an exported class, inline can still be a call into the DLL, so the container was allocated here and freed by the caller. The vector operators were out of line; they now copy through span() in the caller, then clear() on Base's heap.