A modern C++20 utility library bundling networking, concurrency primitives,
buffers, file I/O, compression, and assorted helpers behind a single
SG::common CMake target. Tested on Linux, macOS, Windows (MSVC), and MSYS2.
- A C++20 compiler (GCC, Clang, AppleClang, MSVC);
- CMake 3.15 or newer;
- Boost (headers);
- {fmt} (can use system library, or pull own copy);
- libuv (can use system library, or pull own copy);
- Optional: zstd for compression support.
The recommended way to consume libsg is via
CPM:
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(MyProject CXX)
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake)
include(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake)
CPMAddPackage(
NAME libsg
GITHUB_REPOSITORY samangh/libsg
GIT_TAG master
GIT_SHALLOW
GIT_SUBMODULES_RECURSE ON
OPTIONS
"LIBSG_BUILD_TESTING OFF"
"LIBSG_IMGUI OFF")
add_executable(main main.cpp)
target_link_libraries(main PRIVATE SG::common)Project specific options CMakeLists.txt:
| Option | Default | Description |
|---|---|---|
LIBSG_IMGUI |
ON |
Build the optional ImGui helpers (SG::imgui). |
LIBSG_IMGUI_DIRECTX |
ON |
Enable the DirectX ImGui backend (Windows only). |
LIBSG_IMGUI_OPENGL |
ON |
Enable the OpenGL ImGui backend (default off on Windows). |
LIBSG_ZSTD |
ON |
Build the zstd compression helpers. |
LIBSG_STACKTRACE |
OFF |
Attach stack traces to SG_THROW exceptions. |
LIBSG_EXCEPTION_DETAILS |
OFF |
Attach function/file/line info to SG_THROW exceptions. |
LIBSG_BUILD_TESTING |
ON |
Build the Catch2 test suite (off when a libsg is included as a sub-project). |
LIBSG_BUILD_DOCS |
OFF |
Generate Doxygen docs (only honoured as a sub-project). |
Additional options (from cmake/DefaultPreamble.cmake)
| Option | Default | Description |
|---|---|---|
BUILD_SHARED_LIBS |
ON |
Build shared libraries (set OFF for static). |
USE_STATIC_LIBS |
!BUILD_SHARED_LIBS |
Prefer .a over .so/.dylib when locating external libraries. |
USE_STATIC_RUNTIME |
USE_STATIC_LIBS |
Statically link the C++ runtime (MSVC only). |
IPO |
OFF |
Enable interprocedural / link-time optimisation if the toolchain supports it. |
ARCH_NATIVE |
OFF |
Add -march=native (non-MSVC) or the equivalent SSE/AVX flags. |
USE_SSE |
ARCH_NATIVE |
Enable SSE4.2 / AVX2 (+ CLMUL on x86-64) globally where supported. |
USE_LIBC++ |
OFF |
Build against libc++ (Clang only). |
USE_LINTING |
OFF |
Run clang-tidy on every target via CXX_CLANG_TIDY. |
SANITIZE |
OFF |
Turn on address + UB sanitizers (also exposes SANITIZE_THREAD / SANITIZE_MEMORY). |
COVERAGE |
OFF |
Add -fprofile-instr-generate -fcoverage-mapping and create a coverage target (Clang only). |
OWN_FMT |
OFF |
Fetch & build {fmt} via CPM instead of using a system package. |
OWN_UV |
OFF |
Fetch & build libuv via CPM instead of using a system package. |
BUILD_DOCS |
OFF |
Generate Doxygen documentation (requires Doxygen). |
BUILD_DOCS_SRC |
OFF |
Also scan source files when generating docs (requires BUILD_DOCS). |
There maybe other CMake options available, consult this file.
All public headers live under sg/. Most types are in the sg:: namespace,
with subsystem-specific namespaces (sg::net, sg::compression::zstd, etc.).
tcp_server— accept-loop based server with callback-driven session modeltcp_client— asynchronous TCP client sharing a session abstractiontcp_client_sync— blocking TCPasio_io_pool— restartable thread pool driving a singleboost::asio::io_context- number of utility functions to list all local IP addresses, interfaces and do DNS lookup
worker— interval-driven background thread with start/tick/stop callbacks and on-demandnotify()state_machine<TState>— generic state machine layered on top ofworkerjthread.h— wrapper that usesstd::jthreadwhen available, polyfills it otherwise.
IBuffer<T>interface and theunique_buffer/shared_buffer/unique_opaque_buffer/shared_opaque_bufferfamily, with type-erased deleters (includingdeleter_freefor C-allocated memory).rolling_contiguous_buffer<T>— circular buffer with contiguous storage.enable_lifetime_indicator,pimpl<T>helpers.
channel,channel_vector,channel_rolling,channel_compressed— named, typed data streams sharing a commonIChannelBaseinterface.
sg::common::file::read/writefor whole-file buffer I/O.file_writer— append-only writer with an async queue and dedicated thread.
- One-shot
compress/decompressover raw pointers, contiguous ranges orIBuffer<std::byte>.
sg::checksum::crc32,crc32c(with hardware fast path),crc16.sg::uuids::uuid— value type aroundboost::uuids::uuid.sg::version— comparable dotted version.sg::bytes—byteswap, endian helpers.sg::bounds—upper_bound_index,lower_bound_indexover raw arrays.sg::format,sg::string,sg::ranges,sg::math,sg::map.sg::process— process / thread enumeration.sg::cpu— vendor, model, parallelism estimate.sg::locale— UTF-8 ctype helpers.sg::random::generate<T>(count)— quick shuffled-iota vectors.
Callbacks are declared with the CREATE_CALLBACK macro. Each call generates a
phantom tag type so that two callbacks with the same signature but different
meanings cannot be mistakenly swapped:
#include <sg/callback.h>
CREATE_CALLBACK(on_connected_cb_t, void(int session_id))
CREATE_CALLBACK(on_disconnect_cb_t, void(int session_id))
on_connected_cb_t cb = [](int id) { /* ... */ };
cb.invoke(42); // use .invoke(), not operator()unique_buffer and shared_buffer own a contiguous run of T with a
configurable deleter. Use the _c_ variants when the memory was allocated with
malloc (or any function compatible with free):
#include <sg/buffer.h>
auto buf = sg::make_shared_c_buffer<std::byte>(1024); // free()-backed
std::memcpy(buf.get(), source, 1024);Use the *_opaque_buffer variants when you need to hand a buffer across an API
boundary without exposing the deleter type.
#include <sg/tcp_server.h>
using namespace sg::net;
int main() {
tcp_server::CallBacks cb;
cb.OnSessionDataAvailable =
[](tcp_server& s, auto id, const std::byte* data, size_t size) {
s.write(id, data, size);
};
tcp_server server;
server.start({{"127.0.0.1", 55555}}, cb);
server.future_get_once(); // wait until stopped
}Add connection lifecycle callbacks the same way:
cb.OnSessionCreated = [](tcp_server&, auto id) {
std::cout << "client " << id << " connected\n";
};
cb.OnDisconnected = [](tcp_server&, auto id, std::exception_ptr ex) {
std::cout << "client " << id << " disconnected\n";
};#include <sg/tcp_client_sync.h>
using namespace sg::net;
tcp_client_sync client;
client.connect({"127.0.0.1", 55555});
client.write("hello\n");
auto reply = client.read_until("\n");#include <sg/worker.h>
using namespace std::chrono_literals;
sg::worker w(100ms,
sg::worker::callbacks_t{
.on_start_callback = [](sg::worker*) { /* setup */ },
.on_tick_callback = [](sg::worker*) { /* every 100ms */ },
.on_stop_callback = [](sg::worker*) { /* teardown */ },
});
w.start();
w.notify(); // run the tick immediately
w.request_stop();
w.wait_for_stop();#include <sg/file_writer.h>
sg::file_writer writer;
writer.start("log.bin",
/*on_error*/ nullptr,
/*on_start*/ nullptr,
/*on_stop*/ nullptr);
writer.write_async("hello\n");
writer.stop();#include <sg/compression_zstd.h>
std::vector<std::byte> input = /* ... */;
auto compressed = sg::compression::zstd::compress(
input, sg::compression::zstd::default_compresssion_level(), /*threads*/ 1);
auto roundtrip = sg::compression::zstd::decompress<std::byte>(
compressed.get(), compressed.size());#include <sg/exceptions.h>
namespace myapp::errors {
SG_CREATE_EXCEPTION_ANY(); // myapp::errors::any
SG_CREATE_EXCEPTION(bad_config, "bad config"); // myapp::errors::bad_config
}
try {
SG_THROW(myapp::errors::bad_config, "missing key 'port'");
} catch (const myapp::errors::any& e) { /* ... */ }cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failureTo produce a coverage report (GCC), add -DCOVERAGE=ON and build the
coverage target.
Tests live in test/ and use Catch2.
They are built whenever libsg is the top-level project and discovered via
catch_discover_tests, so any standard CTest invocation will run them.
See LICENSE and the LICENCES directory for third-party notices.