Idle is an asynchronous and hot-reloadable C++ dynamic component framework

Overview

Idle is an asynchronous, hot-reloadable, and highly reactive dynamic component framework similar to OSGI that is:

  • 📦 Modular: Your program logic is encapsulated into services that provide interfaces for usage
  • 🌀 Dynamic: Services can come and go whenever they want, Idle will keep your application stable
  • 🚀 Progressive: Your code is recompiled automatically asynchronously into your application

Therefore Idle can improve your workflow significantly through decreased code iteration time and cleaner code-bases based on the SOLID principles, especially interface segregation. Idle is a C++ engine that is capable of hot-reloading large multi-module codebases on the fly through a scheduled dependency graph. What a game engine is to graphics, that is Idle for C++, without targeting a specific domain. Idle can be used for almost any C++ application type e.g. servers, GUIs, or graphic engines.

Traditional solutions to C++ hot-reloading, such as binary patching, usually do not work when headers are modified, files are added, or code is modified out-of-function or even on optimized release builds. Idle solves this by using shared library loading/unloading techniques that are far more powerful than existing solutions, which are usually limited to simple classes and re-loadable loops. Therefore, Idle can automatically reload headers, compilation units, embedded resources and also takes changes of dependencies and build-system files into account.

Additionally, as C++ becomes more asynchronous in the future, the difficult question of how we can effectively use asynchrony in large applications arises. Idle provides simple patterns and solutions to solve such problems. Furthermore, C++ applications can be prototyped rapidly through Idle's asynchronous ecosystem that provides a CLI, logging, configuration, and persistency.


🔧 Idle is currently in development and therefore not ready for production yet. But, Idle is highly suited already for side-projects, coding playgrounds, and case studies.


Table of Contents

Preview

The following demonstration shows how a multi-module GUI application can be hot-reloaded with Idle:

idle-hotswap-gl.mp4

The actual application shown in this demo is not included in this repository


Features

Dynamic Component System

An application in Idle is based on multiple fundamental concepts:

  • idle::Interface: Provides an arbitrary API for implementation
  • idle::Service: Provides the actual implementation of a running entity inside the system.
  • Clusters that describe a direct relation of an idle::Service to another to allow local configuration.
  • Usages that express a relation of an idle::Service to cluster-external entities.

Therefore Idle provides many improvements over traditional dynamic component systems:

  • Zero run-time overhead and while your system is stable: references to connected interfaces are cached and not mutated while you possibly could access it (except on request, to ensure your service is always thread-safe).
  • Declarative dependencies allow simple dependency configuration without manifests (no XML/JSON).
  • For the locale configuration of services idle uses strongly typed property classes instead of bug-prone string key-value pairs. You fetch the global configuration from your dedicated idle::Config service and propagate these back to nested components!

Asynchronous Ecosystem

To build an asynchronous application, Idle provides a feature-rich and ready to use asynchronous ecosystem based on components and interfaces:

  • Concurrency abstractions:
    • continuable as future/task primitive with optional C++20 coroutine support
    • (event-loop) executor and programmer-friendly custom threads.
  • Asynchronous PluginHotswap:
    • CMake-driven - can reload code in < 2s based on your machine.
    • Compatible with MSBuild and Ninja on Windows and make and Ninja on Posix.
  • Asynchronous FileWatcher (based on efsw)
  • Asynchronous ProcessGroup (base on boost::process)
  • Asynchronous Command interface and a default command processor
  • A terminal repl with command auto-completion and activity progress indicators (based on replxx).
  • Uniform logger facade for pluggable log message sinks (spdlog for example)
  • A configuration system based on a custom reflection system that can adapt to any configuration format (TOML currently implemented).
    • Changes to the configuration file are detected automatically and dependent services are updated accordingly.
    • The configuration file is kept in sync automatically with recently added configuration values and their description.
  • A storage system to persist data across hot-swaps using our reflection system or a user-provided byte buffer.

Networking/HTTP abstractions are considered out-of-scope for this project but might be provided through an external module later


Usage Introduction

This short introduction covers only a small part of the functionality of Idle. To get into the framework we provide you various examples that you can try out dynamically inside the Idle CLI.

Managed Lifetime

Because Idle understands the dependencies of your code and data, it can manage its lifetime much better than you could express it manually. Services are lazy by design and provide an asynchronous onStart and onStop override-able method for managing its lifetime. Thus the application start is automatically parallelized and fast, whereas stopping service cleans up resources safely:

onStop() override { return idle::async([] { puts("HelloWorldService service is being stopped!"); }); } }; ">
class HelloWorldService : public idle::Service {
public:
  using Super::Super;

  continuable<> onStart() override {
    return idle::async([] {
      puts("HelloWorldService service is being started!");
    });
  }
  continuable<> onStop() override {
    return idle::async([] {
      puts("HelloWorldService service is being stopped!");
    });
  }
};

For initialization, services also provide a thread-safe onInit and onDestroy method that is always dispatched on the same event loop (regardless of the thread that initialized or destroyed the service).

class HelloWorldService : public idle::Service {
public:
  using Super::Super;

  void onInit() override {
    IDLE_ASSERT(root().is_on_event_loop());
  }

  void sayHello() {
    puts("Hello!");
  }
};

Initialization always happens automatically before starting the service:

Ref
    context = Context::create();
Ref
   
     hello_world = idle::spawn
    
     (*context);

hello_world->
     start().then([=] {
  hello_world->
     sayHello();
});
    
   
  

Services not referenced anymore are stopped and garbage-collected automatically.

Eager Services through the Autostarted Interface

Because an idle::Service is lazy by design, we have to start it manually through service->start(). Starting a service automatically is supported by the ecosystem itself, rather than the core system. We can export an idle::Autostarted interface from any service, which causes the service to be started automatically (because a global idle::Autostarter service will create a dependency on it).

class AutostartedService : public idle::Implements
    {

   public:
  
   using Super::Super;
};


   IDLE_DECLARE(AutostartedService)
  

The IDLE_DECLAREmacro introduces the AutostartedService to the system and can be used in any executable or plugin directly.

Clean Dependencies through Interfaces

Dependencies in idle are expressed in two ways: parent-child relationships and usages.

idle provides many helper classes for expressing dependencies between services, specialized in cardinality (1:1, 1:n), and flexibility (statically or changeable at run-time).

Every helper class creates Usage objects in different ways and can also be created for a custom purpose.

The Dependency class models a 1:1 non-runtime-changeable dependency, for example, we can make our HelloWorldService depend on the idle::Log as following:

name()); }); } private: idle::Dependency log_{*this}; }; IDLE_DECLARE(HelloWorldService) ">
class HelloWorldService : public idle::Service {
public:
  using Super::Super;
    
  continuable<> onStart() override {
    return idle::async([this] {
      IDLE_LOG_INFO(log_, "{} is being started!", this->name());
    });
  }

private:
  idle::Dependency
     log_{*
    this};
};


    IDLE_DECLARE(HelloWorldService)
   

The Dependency always ensures that the service we depend on is started before the depending service. Additionally, the dependent service stays running until all depending services have stopped.

Idle services encapsulate popular open-source libraries with best practices under the hood.

For example, the log message above is formatted with fmtlib (compile-time fmt) and can be dispatched to your favorite log sink (spdlog, for example).

Declaring an Interface is as easy as it gets. Idle supports default created services and exporting multiple Interface classes from the same Service:

class MyInterface : public idle::Interface {
public:
  using Super::Super;
    
  virtual void doSomething() = 0;
    
  IDLE_INTERFACE //< Optional
};

class MyInterfaceProvider public : idle::Implements
    {

   public:
  
   using Super::Super;

  
   void 
   doSomething() 
   override {
    
   // ...
  }
    
  IDLE_SERVICE 
   //< Optional
};
  

With the included support of weak dependencies, Idle can schedule weakly cyclic dependency graphs, which represent the strongest constrained graph type, that can be scheduled. Cycles are allowed to appear, as long as a dependency can be attached and detached at run-time to and from a depending idle::Service.

Encapsulated and Configurable Components

In this example a nested component is configured from its parent idle::Service.

A configuration file is maintained automatically from the reflected configurations in the system. Services depending on a specific config key, are restarted automatically if a depending value is changed inside the file.

setup(config_.value); } private: idle::Var var_{*this, "config.hello"}; idle::Component my_component_{*this}; }; IDLE_DECLARE(ConfigurableService) ">
struct ConfigurableConfig {
  bool flag{false};
  int value{0};
};

IDLE_REFLECT(ConfigurableConfig, flag, (value, "An optional comment"));

class ConfigurableService : public idle::Service {
public:
  using Super::Super;
    
  void setup(ConfigurableConfig conf) {
    config_ = std::move(conf);
  }

  void onSetup() override {
    my_component_->setup(config_.value);
  }

private:
  idle::Var
      var_{*
     this, 
     "config.hello"};
  idle::Component
     
       my_component_{*
      this};
};


      IDLE_DECLARE(ConfigurableService)
     
    

For a more detailed insight into the framework, further examples are provided.

Installation

Native

Requirements:

  • Windows, Linux, or macOS (not fully tested yet)
  • A C++14 capable compiler: MSVC 2019 (16.7+), Clang 5+, GCC 9+
  • CMake 3.17+
  1. Idle uses custom CMake scripts to download and set up its source dependencies automatically.
  2. A CMake install will build and install a ready-to-use distribution to your preferred install location.
git clone https://github.com/Naios/idle.git
cd idle
mkdir build
mkdir install
cd build

# MSBuild (Visual Studio)
cmake .. -DCMAKE_INSTALL_PREFIX=install
cmake --build . --config Debug  --target INSTALL

# Ninja/make
cmake ..  -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Debug
cmake --build . --target install

../install/bin/idle

# Start editing the project files in `install/project/src`
# Ready-to-use example source files are installed in `install/project/examples`

Docker

A ready-to-build Dockerfile is available inside the docker subdirectory:

  1. ./docker/build.sh: Builds the image with name idle
  2. ./docker/make.sh: Builds idle into build/docker (through mounting the source directory) Make sure that Docker is allowed to mount the idle source directory.
  3. ./docker/shell.sh ./idle: Invokes idle (from build/docker) inside the Docker container

In Docker, Idle supports the automatic rebuilding of source files from a mounted file system due to switching to a polling filewatcher in such cases (controlled with the IDLE_VM environment variable). Therefore it is possible to edit one project that can be automatically tested and live reloaded inside multiple Docker containers at the same time.

How does it Work?

  1. Internally services and their imports and exports are represented as a weakly cyclic directed graph (click to enlarge):

    You can generate your own Graphviz graph as shown above, through the idle graph show [service | cluster] command, which is invocable from the Idle CLI.

  2. Based on this graph, service starts and stops are scheduled, and preferred dependencies are selected

  3. Because we know the topology of the whole application, we can change services effectively at run-time

Issues and Troubleshooting

System Issues/Crashes/Segfaults/Terminations

At its current state, Idle will work mostly out of the box when everything works as expected. The code-base currently lacks proper error/exception handling which will be reworked in a future iteration. For instance, throwing an exception from an idle::Service::onStart method currently leads to an application shutdown, but will properly be handled in the future.

The first step for troubleshooting is to enable the internal logs that are separated from the logger of the ecosystem:

  1. If you are building in Release mode make sure that the CMake option is IDLE_WITH_INTERNAL_LOG=ON is set. In Debug builds Idle is always compiled with internal logging support built-in.
  2. Enable the internal Idle logs by defining the IDLE_INTERNAL_LOGLEVEL environment variable (system-wide or per invocation):
    1. IDLE_INTERNAL_LOGLEVEL=0 ./idle : Runs Idle with trace logs (most detailed but might print sensitive information)
    2. IDLE_INTERNAL_LOGLEVEL=1 ./idle : Runs Idle with debug logs (prints less than trace)
    3. IDLE_INTERNAL_LOGLEVEL=2 ./idle: Runs Idle with error logs (non-fatal errors which need to be handled properly)

Service Dependency Issues

The entire graph of your current Idle system can be exported through GraphViz to many visual formats (pdf, SVG) for explanation purposes, to help debugging issues with resolved dependencies, and to analyze the overall system. This requires that the GraphViz dot program can be found through your systems PATH environment variable.

A graph (in various granularities) can be generated with the idle graph show [service | cluster] command, which can be invoked from the Idle CLI directly.

If you are reporting an issue make sure to attach the graph of your current system as a pdf, an internal trace log as well as any available stack trace logs to your bug report.

Build/HotSwap/Rebuilding Issues

If you are running into issues with rebuilds, try to delete the build directory first. In its default configuration, the build directory is created in idle/project/build. Idle detects a missing build directory on start, and will automatically re-create it for you.

FAQ

  • Is Idle related to an entity-component framework (ECS)?
    • No, services in Idle are meant to provide logical functionality and heavyweight data. Services require a unique position in memory and are mostly dynamically or statically allocated. They are not cache-optimized nor recommended for representing an entity where you expect to have thousands of instances alive or in iteration simultaneously. We recommend using an ECS from inside a service instead of making a service part of an ECS.
  • Does Idle hot-swap require a specific platform, compiler flag, or toolchain?
    • No, theoretically Idle hot-swap can be used on every platform that supports dynamic runtime linking. Because dependencies are tracked and swapped on a per-service level, this will even work on heavily optimized release builds and can be used theoretically in production as well.
  • Is it possible to ship my application with static instead of dynamic linkage?
    • You can statically link your plugins fully into your idle application without any change in your source code. Idle can even read your IDLE_DECLARE definitions from your statical linked executable and instantiate the exported services as they were exported from a plugin. A recommended workflow is to develop your application by using dynamic linking and automatic hot-swapping for a short code iteration while distributing it with static linking and no hot-swap. Through that, static lifetime differences between platforms and build types will belong to the past.
  • Is it possible to disable RTTI?
    • Idle uses RTTI for improved naming of your services only, if available, and can be used without RTTI. Naming support through the IDLE_SERVICE, IDLE_INTERFACE, and IDLE_PART macros are implemented without RTTI through constexpr type reflection (__PRETTY_FUNCTION__ parsing).
  • Why is this software licensed under AGPL v3? Is it planned to relicense it?
    • Idle is licensed under the AGPL until the project has grown in maturity and we can narrow the project's direction. Different parts of the project might be relicensed/sublicensed to a more permissive license to make it available to a broader audience. I'm currently looking into possibilities to fund this project properly, and therefore licensing it now for open commercial use, would limit my options.
Issues
  • Use with vcpkg

    Use with vcpkg

    This projects is very cool and I want to use it in my own project, but I use vcpkg for all my dependencies. I would also create a PR to add this lib to vcpkg, but I have found the following statement:

    Idle uses custom CMake scripts to download and set up its source dependencies automatically.

    which is forbidden for vcpkg packages. Is there an option to disable this and is there a list of required dependencies?

    opened by autoantwort 7
Owner
Denis Blank
⭐️ Compiler and C++ metaprogramming enthusiast
Denis Blank
Kigs framework is a C++ modular multipurpose cross platform framework.

Kigs framework is a C++ modular multi-purpose cross-platform framework. It was used as a basis for many professionnal projects. The main goal was to b

null 68 Jun 23, 2022
JUCE is an open-source cross-platform C++ application framework for desktop and mobile applications, including VST, VST3, AU, AUv3, RTAS and AAX audio plug-ins.

JUCE is an open-source cross-platform C++ application framework used for rapidly developing high quality desktop and mobile applications, including VS

JUCE 4.3k Jul 3, 2022
An eventing framework for building high performance and high scalability systems in C.

NOTE: THIS PROJECT HAS BEEN DEPRECATED AND IS NO LONGER ACTIVELY MAINTAINED As of 2019-03-08, this project will no longer be maintained and will be ar

Facebook Archive 1.7k Jun 15, 2022
Framework for Enterprise Application Development in c++, HTTP1/HTTP2/HTTP3 compliant, Supports multiple server backends

The ffead-cpp Framework ffead-cpp is a web-framework, application framework, utilities all bundled into one. It also provides an embedded HTTP/Web-Soc

Sumeet Chhetri 514 Jun 7, 2022
🔥 bhook(aka ByteHook) is a PLT hook framework for Android app.

?? bhook(aka ByteHook) is a PLT hook framework for Android app. Most of ByteDance's Android apps use bhook as the PLT hook solution online.

Bytedance Inc. 1.2k Jun 30, 2022
PYNQ Framework for ANTSDR

PYNQ Framework for ANTSDR This project was inspired by PYNQ and PlutoSDR. There are already many SDR platforms based on ZYNQ and AD9361, so does ANTSD

null 18 May 25, 2022
KoanLogic 378 Jun 22, 2022
A toolkit for making real world machine learning and data analysis applications in C++

dlib C++ library Dlib is a modern C++ toolkit containing machine learning algorithms and tools for creating complex software in C++ to solve real worl

Davis E. King 11.2k Jun 22, 2022
EASTL stands for Electronic Arts Standard Template Library. It is an extensive and robust implementation that has an emphasis on high performance.

EA Standard Template Library EASTL stands for Electronic Arts Standard Template Library. It is a C++ template library of containers, algorithms, and i

Electronic Arts 6.5k Jun 29, 2022
An open-source C++ library developed and used at Facebook.

Folly: Facebook Open-source Library What is folly? Folly (acronymed loosely after Facebook Open Source Library) is a library of C++14 components desig

Facebook 22.4k Jul 1, 2022
Functional Programming Library for C++. Write concise and readable C++ code.

FunctionalPlus helps you write concise and readable C++ code. Table of contents Introduction Usage examples Type deduction and useful error messages T

Tobias Hermann 1.6k Jun 24, 2022
C++14 evented IO libraries for high performance networking and media based applications

LibSourcey C++ Networking Evolved LibSourcey is a collection of cross platform C++14 modules and classes that provide developers with an arsenal for r

Sourcey 1.2k Jun 9, 2022
Easy to use, header only, macro generated, generic and type-safe Data Structures in C

C Macro Collections Easy to use, header only, macro generated, generic and type-safe Data Structures in C. Table of Contents Installation Contributing

Leonardo Vencovsky 307 Jun 28, 2022
A collection of single-file C libraries. (generic containers, random number generation, argument parsing and other functionalities)

cauldron A collection of single-file C libraries and tools with the goal to be portable and modifiable. Libraries library description arena-allocator.

Camel Coder 31 Jun 17, 2022
A C# hot reload framework for Unity3D, based on Mono's MONO_AOT_MODE_INTERP mode.

PureScript 一个支持Unity3D的C#热更框架,基于Mono的MONO_AOT_MODE_INTERP模式。 支持在iOS平台Assembly.Load 构建时自动绑定Unity的Il2cpp代码。 支持大部分Unity特性,包括MonoBehaviour、Coroutine。 支持配置

null 257 Jun 25, 2022
x64Dbg plugin that enables C# plugins with hot-loading support and scripting.

DotX64Dbg (EARLY ALPHA) Plugins and Scripting with C# for x64Dbg. Create Plugins for X64Dbg with ease DotX64Dbg aims to provide a seamless way to writ

x64dbg 82 Jun 7, 2022
roost low profile ergonomic ortholinear hot-swappable kailh-choc keyboard

Roost keyboard Based off the Sweep v2.1, added 2mm or so horizontal space, added another set of keys and slots for a fat power switch. Moved battery c

Alksdeef 33 Jun 27, 2022
Thick, girthy, hot, griplling tentacles.

Thick, girthy, hot, griplling tentacles. please go away i did the huge mistake of giving these idiots write access I've come to make an announcement:

null 3 Feb 8, 2022
A UE4 plugin wrapper for Molecular Matter's Live++ Hot-Reloading Library

Good News Everyone: Natively integrated in UE4.22+ Details here: https://docs.unrealengine.com/en-us/Builds/4_22 UE4 LivePP: C/C++ live coding A UE4 p

Kite & Lightning 99 May 1, 2022
A self-contained minimal library for interacting with Linux hot-plug events

libue Zero dependency minimal library for interacting with Linux hot-plug events. Installation Just drop the header file into your C project. Usage #i

QP Hou 20 Feb 10, 2022
FastDynamicCast - Fast dynamic cast in C++ for MSVC, outperforming the regular dynamic cast by up to 25 times

Fast dynamic cast This is a single header, dynamic cast implementation which outperforms the regular dynamic_cast by up to 25 times. Works on MSVC 201

tobspr 83 May 8, 2022
Enabling services on your device 66 Jun 23, 2022
Corvusoft's Restbed framework brings asynchronous RESTful functionality to C++14 applications.

Restbed Restbed is a comprehensive and consistent programming model for building applications that require seamless and secure communication over HTTP

Corvusoft 1.7k Jun 21, 2022
An asynchronous web framework for C++ built on top of Qt

!!! I can no longer maintain this project. If you're interessed, please contact me and I can move the projetct to you !!! Tufão - an asynchronous web

Vinícius dos Santos Oliveira 542 Jun 14, 2022
Corvusoft's Restbed framework brings asynchronous RESTful functionality to C++14 applications.

Restbed Restbed is a comprehensive and consistent programming model for building applications that require seamless and secure communication over HTTP

Corvusoft 1.7k Jun 24, 2022
Corvusoft's Restbed framework brings asynchronous RESTful functionality to C++14 applications.

Restbed Restbed is a comprehensive and consistent programming model for building applications that require seamless and secure communication over HTTP

Corvusoft 1.4k Mar 8, 2021
Elle - The Elle coroutine-based asynchronous C++ development framework.

Elle, the coroutine-based asynchronous C++ development framework Elle is a collection of libraries, written in modern C++ (C++14). It contains a rich

Infinit 463 Jun 3, 2022
Qt based simple SCADA framework, with dashboard, static and dynamic components

QSimpleScada Qt/C++ based simple SCADA library for your IoT projects. We created QSimpleScada to speed up and simplify visualising any data, so we (an

Indeema Software Inc. 159 Jun 21, 2022
Maat is an open-source Dynamic Symbolic Execution and Binary Analysis framework

About Maat is an open-source Dynamic Symbolic Execution and Binary Analysis framework. It provides various functionalities such as symbolic execution,

Trail of Bits 444 Jun 22, 2022