Skip to content

Commit

Permalink
project init.
Browse files Browse the repository at this point in the history
  • Loading branch information
alkavan committed Aug 29, 2024
0 parents commit 308c686
Show file tree
Hide file tree
Showing 8 changed files with 550 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea
.vs
!.gitkeep
doc/
cmake-build-*
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.29)
project(microbus)

set(CMAKE_CXX_STANDARD 17)

add_executable(microbus_examples src/examples.cpp)
34 changes: 34 additions & 0 deletions Doxyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Doxyfile 1.12.0

PROJECT_NAME = "microbus"

OUTPUT_DIRECTORY = doc

INPUT = src/microbus.hpp
RECURSIVE = NO
EXCLUDE_PATTERNS = */test/*
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES

HAVE_DOT = YES
DOT_MULTI_TARGETS = YES
STRIP_FROM_PATH =

WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES

GENERATE_HTML = YES
GENERATE_LATEX = YES
GENERATE_MAN = NO
GENERATE_XML = NO
GENERATE_RTF = NO

PDF_HYPERLINKS = YES
DOCUMENT_CLASS = "article"

CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
GRAPHICAL_HIERARCHY = YES
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Igal Alkon

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# microbus - A simple event-bus and event-loop header-only library

The `microbus` namespace provides a simple yet efficient event-bus and event-loop implementation in C++17.
This library includes classes to manage subscriptions, event notifications, and asynchronous event processing.

## Overview

### Classes

- **type_erased_handler**: An abstract base class for type-erased handlers.
- **concrete_handler**: A concrete handler that binds a function to a given set of arguments.
- **event_bus**: Manages event subscriptions and notifications.
- **event_loop**: Processes asynchronous events.

## Detailed Description

### `type_erased_handler`

This is a base class for handlers that erase type information. It provides a virtual `invoke` method which derived classes must implement.

### `concrete_handler`

A templated class that derives from `type_erased_handler`. It binds a specific function (handler) to a set of arguments. It overrides the `invoke` method to call the stored function with the provided arguments.

### `event_bus`

This class manages event subscriptions and notifications. It allows users to subscribe to events, unsubscribe, and trigger events.

- **subscribe**: Allows a client to subscribe to an event with a specified handler. Returns a unique subscription ID.
- **unsubscribe**: Unsubscribes a client from an event using the event name and subscription ID.
- **trigger**: Triggers an event, calling all subscribed handlers with provided arguments.
- **clear**: Clears all event subscriptions.

### `event_loop`

This class manages the processing of asynchronous events. It runs an internal thread to process events queued for execution.

- **enqueue_event**: Enqueues an event for asynchronous processing. The event will be processed by calling the relevant handlers from the `event_bus`.
- **wait_until_finished**: Blocks until all events in the queue are processed.
- **stop**: Stops the event loop and joins the internal thread.

## Key Features

- **Thread-Safe**: Both `event_bus` and `event_loop` classes use mutexes to ensure thread-safety.
- **Type Erasure**: Handlers are type-erased, allowing storage of various callable objects in a unified manner.
- **Asynchronous Processing**: Events can be processed asynchronously using the `event_loop` class.
- **Automatic Cleanup**: Event handlers are automatically cleaned up when unsubscribed or when the event bus is cleared.
- **Flexible Subscription Management**: Supports multiple subscribers per event and manages them using unique subscription IDs.

## Example Usage

This example demonstrates subscribing to an event,
triggering it synchronously and asynchronously,
and then stopping the event loop.

```cpp
#include <iostream>
#include <memory>
#include "microbus.hpp"

int main() {
auto bus = std::make_shared<microbus::event_bus>();
microbus::event_loop loop;

// Subscribe to event
int subscription_id = bus->subscribe<int>("test_event", [](int value) {
std::cout << "Received event with value: " << value << std::endl;
});

// Trigger the event synchronously
bus->trigger("test_event", 42);

// Queue the event for asynchronous processing
loop.enqueue_event(bus, "test_event", 100);

// Wait for all events to be processed
loop.wait_until_finished();

// Unsubscribe from the event
bus->unsubscribe("test_event", subscription_id);

// Stop the event loop
loop.stop();

return 0;
}
```
Empty file added doc/.gitkeep
Empty file.
118 changes: 118 additions & 0 deletions src/examples.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "microbus.hpp"

#include <iostream>
#include <thread>
#include <chrono>
#include <numeric>
#include <vector>

static int64_t factorial(int n);

int main() {
microbus::event_bus events;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Simple event bus example

// Define event handler types.
using calc_event_handler = microbus::event_bus::event_handler<double, int>;
using message_event_handler = microbus::event_bus::event_handler<std::string>;

// Multiply PI event handler
calc_event_handler multiply_by_pi = [](double value, int multiply_by) {
std::cout << "Multiplying " << value << " by " << multiply_by << " is " << (value * multiply_by) << std::endl;
};
int number_event_subscription_id = events.subscribe<double, int>("OnCalc", multiply_by_pi);

// Message variable from main scope captured by lambda
std::string greeting = "Hello, ";

// Message event handler
message_event_handler on_message = [&](const std::string &message) {
std::cout << greeting << message << std::endl;
};
int message_event_subscription_id = events.subscribe<std::string>("OnMessage", on_message);

auto pie = 3.14159265;

// Trigger events
events.trigger("OnCalc", pie, 4);
events.trigger("OnMessage", std::string("Joe"));

// Unsubscribe the message event
events.unsubscribe("OnMessage", message_event_subscription_id);

// Trigger events after unsubscribing
events.trigger("OnCalc", pie, 8);
events.trigger("OnMessage", std::string("Jane")); // This won't produce output

// Clear all events
events.clear();

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Smart pointer passed off-thread example

using smart_pointer_event_handler = microbus::event_bus::event_handler<std::shared_ptr<std::string>>;

// Message event handler
smart_pointer_event_handler on_thread_message = [&](const std::shared_ptr<std::string>& message) {
std::cout << "Received message: " << *message << std::endl;
};

int smart_pointer_event_subscription_id = events.subscribe<std::shared_ptr<std::string>>("OnSmartPtrMessage", on_thread_message);

// Function to trigger the event from another thread
auto trigger_event_from_thread = [&events]() {
auto message = std::make_shared<std::string>("Hello from another thread!");
events.trigger("OnSmartPtrMessage", message);
};

// Create and launch a new thread
std::thread event_thread(trigger_event_from_thread);

// Wait for the event thread to finish
event_thread.join();

// Clear event
events.unsubscribe("OnSmartPtrMessage", smart_pointer_event_subscription_id);
events.clear();

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Shared event bus and event loop example

std::shared_ptr<microbus::event_bus> shared_event_bus = std::make_shared<microbus::event_bus>(); // Ensure the use of shared_ptr
microbus::event_loop event_loop;

using factorial_event_handler = microbus::event_bus::event_handler<int>;

factorial_event_handler compute_factorial = [](int number) {
auto result = factorial(number);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "Factorial of " << number << " is " << result << std::endl;
};

shared_event_bus->subscribe<int>("OnFactorial", compute_factorial);

std::vector<uint64_t> numbers_to_factorial1 = {15, 17, 19};
std::vector<uint64_t> numbers_to_factorial2 = {16, 18, 20};

for (uint64_t number : numbers_to_factorial1) {
event_loop.enqueue_event(shared_event_bus, "OnFactorial", number);
}

for (uint64_t number : numbers_to_factorial2) {
event_loop.enqueue_event(shared_event_bus, "OnFactorial", number);
}

// Wait until loop finishes handling all events
event_loop.wait_until_finished();
event_loop.stop();

shared_event_bus->clear();
}

static int64_t factorial(int n) {
std::vector<int> numbers(n);
std::iota(numbers.begin(), numbers.end(), 1);
return std::accumulate(numbers.begin(), numbers.end(), 1LL, std::multiplies<>());
}
Loading

0 comments on commit 308c686

Please sign in to comment.