Skip to content

friendlyshade/shoto

Repository files navigation

shoto-logo

Shoto

Robust, high-performance, extremely memory-efficient C++ signal-slot library.

Motivation

As a long-time user of Qt and the observer design pattern, I rely on signals and slots as a daily programming tool. They offer a general-purpose, expressive way to decouple components, making complex systems easier to design and maintain.

But over time, I’ve noticed a recurring problem: bringing Qt into the core of a project often meant pulling in far more than I actually needed. The framework is large, opinionated, and difficult to manage in environments where multiple modules or plugins may depend on different Qt versions. In such cases, Qt’s strengths quickly turn into overhead, complexity, and even runtime conflicts.

I explored various open-source signal-slot libraries, hoping for a lightweight alternative. However, most were slower, more memory-intensive, thread-unsafe, or naively designed compared with Qt’s implementation. None struck the right balance between performance, simplicity, and independence from heavy dependencies.

This gap motivated me to build a solution focused on what I truly needed: a fast, memory-efficient, and dependency-free signal-slot system that could serve as a reliable foundation for any project, without dragging in the weight of an entire framework.

Features

Core Advantages

  • Zero dependencies
  • Header-only
  • Thread-safe
  • Simple, clean, feature-complete API
  • Well-documented (reference docs, guides, and examples)
  • Extensive test coverage
  • Up to 4× faster than Qt for connect/disconnect/emit/destroy (see benchmarks)
  • Low memory footprint: ≈100 MB per 1M connections (~100 B/conn) — lower than Qt and other open-source alternatives (see benchmarks)

Signal-Slot Capabilities

  • Connection to member, lambda, and free functions (trackable via Trackable)
  • Automatic connection cleanup when either the signal or trackable is destroyed
  • Deterministic connect-order execution: slots are invoked in the exact order they were connected
  • Explicit disconnection via connection handle
  • Explicit disconnection by signal
  • Explicit disconnection by trackable (receiver)
  • Explicit disconnection by method
  • Explicit disconnection by a combination of signal, trackable, and (optionally) method
  • Allocation-free signal emission
  • O(1) connection and disconnection
  • Recursive emission (in-slot emission)
  • Safe connection and disconnection during emission, including recursive cases
  • Multiple-inheritance support (see samples)
  • Thread-safe
    • Concurrent connection, disconnection, and emission
    • Safe connection and disconnection during recursive emissions
    • Lock-free slot invocation (non-blocking call path)
    • Low memory overhead
    • Low latency signal dispatching
    • Contention-optimized: adaptive spin-lock strategy, fine-tuned separately for Linux and Windows to minimize latency spikes

Real-time & game development

Shoto is suitable for real-time and game development:

  • Emission is allocation-free
  • Non-thread-safe mode is extremely lightweight and ideal for main-thread gameplay and other hot paths
  • Thread-safe mode provides strong lifetime safety and cross-thread signaling, without holding locks while user slots run

Roadmap

  • Const slots
  • Signal-to-signal connection
  • Signal blocking
  • Thread-safe Trackable’s move constructor/operator (under consideration)
  • Thread-safe Signal’s move constructor/operator (under consideration)
  • Signal/Trackable destruction during emission (perhaps deferring destruction with a "delete_later" flag? or a ref counter?) (under consideration)
  • Exception-safety (under consideration)
  • Return values (under consideration)
  • Copy semantics
  • macOS support
  • Clang compiler support

Compatibility

  • Platforms: Windows and Linux
  • Architectures: x86-64 (x64/AMD64)
  • Compilers: MSVC and GCC
  • C++ Standard: C++17 (minimum)
  • CMake: 3.29 (minimum)

Dependencies

Welcome to dependency heaven! No external libraries required, only a C++17 (or newer) compiler.

Usage

Hello World (minimal example)

Demonstrates connecting a lambda function directly to a signal without a trackable object.

#include <shadercode/shoto.h>

#include <cstdio>
#include <string>

int main()
{
    // sender
    shoto::Signal<const std::string&> signal;

    // connect a lambda (connection lifetime is tied to the signal, and optionally a Trackable)
    shoto::connect(&signal, [](const std::string& str) {
        std::printf("Lambda got '%s'\n", str.c_str());
    });

    // emit signal
    signal("Sebastian Zapata was here!");
}

Multiple-inheritance

Trackable in Derived / slot in Base

Derived (Logger) inherits from Trackable. Note that the slot doesn’t have to be declared in a type that derives from Trackable.

#include <shadercode/shoto.h>
#include <cstdio>

using shoto::Signal;
using shoto::Trackable;
using shoto::Connection;

// slot declaring type (doesn't need to be Trackable)
struct LoggerBase
{
    virtual void onMsg() { std::printf("Hello World from Base\n"); };
};

struct Logger : Trackable, LoggerBase
{
    void onMsg() override { std::printf("Hello World from Logger\n"); };
};

int main()
{
    Signal<> sig;
    Logger log;
    Connection::connect(&sig, &log, &LoggerBase::onMsg);
    sig();
}

Testing

Linux

Note

When ThreadSanitizer is enabled with SHOTO_ENABLE_THREAD_SANITIZER, you may see an error such as FATAL: ThreadSanitizer: unexpected memory mapping 0x.... This is a known issue between TSan and certain Linux kernel versions. To work around it, run:

sudo sysctl vm.mmap_rnd_bits=28

or a similar lower value.

Warning

The sudo sysctl vm.mmap_rnd_bits=28 command lowers the ASLR entropy for the mmap area to 28 bits. It’s useful as a development/workaround knob but weakens security, so keep this value as high as possible and avoid using it on production systems.

Implementation details

  • In-slot connections and disconnections could, in theory, be deferred to eliminate contention during emission. However, this would introduce significant memory overhead, especially during concurrent emission and disconnection, which goes against one of the library’s primary goals: minimal memory usage.

Contributor Confidentiality and IP Agreement

By accessing this repository, you agree to the following terms:

  1. Confidentiality
    This project and all its contents are proprietary and confidential.
    You may not share, distribute, or disclose any part of the codebase, documentation, or related assets without prior written permission from Friendly Shade, Inc. or Sebastian Zapata.

  2. Intellectual Property
    All contributions to this repository, whether code, documentation, or other material, become the property of Friendly Shade, Inc. and Sebastian Zapata.
    Contributors waive any individual ownership rights to their submissions.

  3. Restrictions
    Unauthorized use, copying, or modification of this project outside the scope of authorized collaboration is strictly prohibited.

Copyright Notice

© 2025 Friendly Shade, Inc. All rights reserved.
© 2025 Sebastian Zapata. All rights reserved.

This repository and its contents are proprietary and confidential. Unauthorized copying, distribution, or disclosure of any part of this project is strictly prohibited.

Shader Code™ and Friendly Shade™ are trademarks of Friendly Shade, Inc.