From bf945e65fff64e7caeb727045583fb740cb0c97c Mon Sep 17 00:00:00 2001 From: Michael de Lang Date: Fri, 24 Nov 2023 15:35:46 +0100 Subject: [PATCH] Add guiding texts to examples and cleanup some things --- README.md | 2 +- .../ichor_serializer_benchmark.cpp | 106 +++++------------- docs/04-WhyUseIchor.md | 14 ++- examples/common/TestMsgBoostJsonSerializer.h | 42 ------- examples/etcd_example/main.cpp | 1 + examples/event_statistics_example/README.md | 38 +++++++ examples/event_statistics_example/main.cpp | 1 + examples/http_example/README.md | 20 ++++ examples/http_example/main.cpp | 6 + examples/http_ping_pong/ping.cpp | 1 + examples/http_ping_pong/pong.cpp | 1 + examples/minimal_example/README.md | 9 ++ examples/multithreaded_example/README.md | 8 ++ examples/multithreaded_example/main.cpp | 1 + .../optional_dependency_example/README.md | 8 ++ examples/optional_dependency_example/main.cpp | 1 + examples/realtime_example/README.md | 3 + examples/serializer_example/README.md | 25 +++++ examples/serializer_example/main.cpp | 1 + examples/tcp_example/README.md | 3 + examples/tcp_example/main.cpp | 1 + examples/timer_example/README.md | 3 + examples/timer_example/main.cpp | 1 + examples/tracker_example/README.md | 12 ++ examples/tracker_example/TrackerService.h | 11 ++ examples/tracker_example/main.cpp | 1 + examples/websocket_example/README.md | 20 ++++ examples/websocket_example/main.cpp | 1 + examples/yielding_timer_example/README.md | 3 + examples/yielding_timer_example/main.cpp | 1 + 30 files changed, 218 insertions(+), 127 deletions(-) delete mode 100644 examples/common/TestMsgBoostJsonSerializer.h create mode 100644 examples/event_statistics_example/README.md create mode 100644 examples/http_example/README.md create mode 100644 examples/minimal_example/README.md create mode 100644 examples/multithreaded_example/README.md create mode 100644 examples/optional_dependency_example/README.md create mode 100644 examples/realtime_example/README.md create mode 100644 examples/serializer_example/README.md create mode 100644 examples/tcp_example/README.md create mode 100644 examples/timer_example/README.md create mode 100644 examples/tracker_example/README.md create mode 100644 examples/websocket_example/README.md create mode 100644 examples/yielding_timer_example/README.md diff --git a/README.md b/README.md index b33738dc..6396031b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ TL;DR: Node.js-style event loops with coroutines and dependency injection, excep Ichor borrows from the concept of [Fearless Concurrency](https://doc.rust-lang.org/book/ch16-00-concurrency.html) and offers thread confinement. -# Boost supports event loops, dependency injection, networking, can do all of that, and more. Any reason i'd want to use your lib instead? +### Boost supports event loops, dependency injection, networking, can do all of that, and more. Any reason i'd want to use your lib instead? Excellent question! Please see [this page](./docs/04-WhyUseIchor.md) to get a more in-depth answer. diff --git a/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp b/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp index 2efe06df..83d8338d 100644 --- a/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp +++ b/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp @@ -8,7 +8,6 @@ #include #include #include "../../examples/common/TestMsgRapidJsonSerializer.h" -#include "../../examples/common/TestMsgBoostJsonSerializer.h" #include "../../examples/common/lyra.hpp" uint64_t sizeof_test{}; @@ -19,14 +18,8 @@ int main(int argc, char *argv[]) { bool showHelp{}; bool singleOnly{}; - bool rapidjsonOnly{}; - [[maybe_unused]] bool boostjsonOnly{}; auto cli = lyra::help(showHelp) - | lyra::opt(rapidjsonOnly)["-r"]["--rapid-json"]("enable only rapid json") -#ifdef ICHOR_USE_BOOST_JSON - | lyra::opt(boostjsonOnly)["-b"]["--boost-json"]("enable only boost json") -#endif | lyra::opt(singleOnly)["-s"]["--single"]("Single core only"); auto result = cli.parse( { argc, argv } ); @@ -40,83 +33,40 @@ int main(int argc, char *argv[]) { return 0; } - if((!rapidjsonOnly && !boostjsonOnly) || rapidjsonOnly) { - { - auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); - auto &dm = queue->createManager(); - dm.createServiceManager, ILoggerFactory>(); - dm.createServiceManager>(); - dm.createServiceManager(); - queue->start(CaptureSigInt); - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} single threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS(), - std::floor(1'000'000. / static_cast(std::chrono::duration_cast(end - start).count()) * SERDE_COUNT * static_cast(sizeof_test) / 1'000'000.)); - } - - if(!singleOnly) { - auto start = std::chrono::steady_clock::now(); - std::array threads{}; - std::array queues{}; - for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) { - threads[i] = std::thread([&queues, i] { - auto &dm = queues[i].createManager(); - dm.createServiceManager, ILoggerFactory>(); - dm.createServiceManager>(); - dm.createServiceManager(); - queues[i].start(CaptureSigInt); - }); - } - for (uint_fast32_t i = 0; i < threadCount; i++) { - threads[i].join(); - } - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} multi threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", - argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS(), - std::floor(1'000'000. / static_cast(std::chrono::duration_cast(end - start).count()) * SERDE_COUNT * threadCount * static_cast(sizeof_test) / 1'000'000.)); - } + auto start = std::chrono::steady_clock::now(); + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + dm.createServiceManager, ILoggerFactory>(); + dm.createServiceManager>(); + dm.createServiceManager(); + queue->start(CaptureSigInt); + auto end = std::chrono::steady_clock::now(); + std::cout << fmt::format("{} single threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(end - start).count()) * SERDE_COUNT * static_cast(sizeof_test) / 1'000'000.)); } -#ifdef ICHOR_USE_BOOST_JSON - if((!rapidjsonOnly && !boostjsonOnly) || boostjsonOnly) - { - { - auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); - auto &dm = queue->createManager(); - dm.createServiceManager, ILoggerFactory>(); - dm.createServiceManager>(); - dm.createServiceManager(); - queue->start(CaptureSigInt); - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} single threaded boost.JSON ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS(), - std::floor(1'000'000. / static_cast(std::chrono::duration_cast(end - start).count()) * SERDE_COUNT * static_cast(sizeof_test) / 1'000'000.)); + if(!singleOnly) { + auto start = std::chrono::steady_clock::now(); + std::array threads{}; + std::array queues{}; + for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) { + threads[i] = std::thread([&queues, i] { + auto &dm = queues[i].createManager(); + dm.createServiceManager, ILoggerFactory>(); + dm.createServiceManager>(); + dm.createServiceManager(); + queues[i].start(CaptureSigInt); + }); } - - if(!singleOnly) { - auto start = std::chrono::steady_clock::now(); - std::array threads{}; - std::array queues{}; - for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) { - threads[i] = std::thread([&queues, i] { - auto &dm = queues[i].createManager(); - dm.createServiceManager, ILoggerFactory>(); - dm.createServiceManager>(); - dm.createServiceManager(); - queues[i].start(CaptureSigInt); - }); - } - for (uint_fast32_t i = 0; i < threadCount; i++) { - threads[i].join(); - } - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} multi threaded boost.JSON ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", - argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS(), - std::floor(1'000'000. / static_cast(std::chrono::duration_cast(end - start).count()) * SERDE_COUNT * threadCount * static_cast(sizeof_test) / 1'000'000.)); + for (uint_fast32_t i = 0; i < threadCount; i++) { + threads[i].join(); } + auto end = std::chrono::steady_clock::now(); + std::cout << fmt::format("{} multi threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", + argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(end - start).count()) * SERDE_COUNT * threadCount * static_cast(sizeof_test) / 1'000'000.)); } -#endif return 0; } diff --git a/docs/04-WhyUseIchor.md b/docs/04-WhyUseIchor.md index a4726d5a..f9a28345 100644 --- a/docs/04-WhyUseIchor.md +++ b/docs/04-WhyUseIchor.md @@ -30,14 +30,16 @@ Here's a short hand comparison between Ichor and various DI libraries: ||Ichor|Hypodermic|Boost.DI|Google.Fruit|CppMicroservices| |:-|:-|:-|:-|:-|:-| |Runtime/compile-time|Runtime|Runtime|Compile time (with some runtime)|Compile time (with some runtime)|Runtime| -|Constructor Injection|Yes|Yes|Yes|Yes|No| -|Per-instance resolving|Yes|No|No|No|Yes| -|Factories|Yes|No?|Partial|Partial|Yes| -|Coroutines|Yes|No|No|No|No| -|Optional dependencies|Yes|No|No|No|Yes?| -|Per-instance lifetime|Yes|No|No|No|Yes| +|Constructor Injection|✅|✅|✅|✅|❌| +|Per-instance resolving|✅|❌|❌|❌|✅| +|Factories|✅|❌?|Partial|Partial|✅| +|Coroutines|✅|❌|❌|❌|❌| +|Optional dependencies|✅|❌|❌|❌|✅?| +|Per-instance lifetime|✅|❌|❌|❌|✅| |Minimum c++|20|11|14|11|17| +Question marks denote me being unsure + # But Boost.BEAST also provides https and websockets. Why would I use your lib? If you need all the bells and whistles that Boost.BEAST provides, then by all means, use that. If you need performance, the techempowered benchmarks will probably tell you to use Drogon or something similar. Ichor has a gap here. diff --git a/examples/common/TestMsgBoostJsonSerializer.h b/examples/common/TestMsgBoostJsonSerializer.h deleted file mode 100644 index 2da3f38b..00000000 --- a/examples/common/TestMsgBoostJsonSerializer.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#ifdef ICHOR_USE_BOOST_JSON - -//#include -#include -#include -#include "TestMsg.h" - -#pragma GCC diagnostic push -#ifndef __clang__ -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif -#pragma GCC diagnostic ignored "-Wcast-align" -#pragma GCC diagnostic ignored "-Wdeprecated-copy" -#pragma GCC diagnostic ignored "-Warray-bounds" -#include -#pragma GCC diagnostic pop - -using namespace Ichor; - -class TestMsgBoostJsonSerializer final : public ISerializer, public AdvancedService { -public: - TestMsgBoostJsonSerializer() = default; - ~TestMsgBoostJsonSerializer() final = default; - - std::vector serialize(TestMsg const &msg) final { - boost::json::value jv = {{"id", msg.id}, {"val", msg.val}}; - auto str = boost::json::serialize(jv); - return std::vector(str.begin(), str.end()); - } - - std::optional deserialize(std::vector &&stream) final { - unsigned char buffer[4096]; - boost::json::monotonic_resource mr{buffer}; - boost::json::value value = boost::json::parse(std::string_view{reinterpret_cast(stream.data()), stream.size()}, &mr); - return TestMsg{boost::json::value_to(value.at("id")), boost::json::value_to(value.at("val"))}; - } -}; - -#endif diff --git a/examples/etcd_example/main.cpp b/examples/etcd_example/main.cpp index 54e73e4d..d41d7484 100644 --- a/examples/etcd_example/main.cpp +++ b/examples/etcd_example/main.cpp @@ -3,6 +3,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/event_statistics_example/README.md b/examples/event_statistics_example/README.md new file mode 100644 index 00000000..7413510b --- /dev/null +++ b/examples/event_statistics_example/README.md @@ -0,0 +1,38 @@ +# Event Statistics Example + +This example showcases Ichor's capability to hook into the event loop system. It uses the pre- and post-event hooks to determine how long each event took to resolve and prints out the min/max/avg per event type at exit. + +It uses the `EventStatisticsService` provided by ichor, which in turn uses `preInterceptEvent` and `postInterceptEvent`. + +This demonstrates the following concepts: +* Using the provided `EventStatisticsService` for performance measurements +* Using the Timer subsystem to generate some artificial load to measure +* How to implement ones own Event Interceptor service (see below) + +Implementing such a service yourself is also possible by calling the `registerEventInterceptor<>` function on the Dependency Manager: + +```c++ +class MyClass { +public: + MyClass(DependencyManager *dm) { + // register the interceptor and keep a reference it, as the destructor automatically unregisters it. + _interceptorRegistration = dm->registerEventInterceptor(this, this); + } + + void preInterceptEvent(Event const &evt) { + // We don't need to check for evt.id or evt.type here, as postInterceptEvent() is guaranteed to be called directly after processing the event + // The downside is then that each async execution point of an event is counted as a separate instance. I.e. every co_await is a new event with its own processing time. + _startProcessingTimestamp = std::chrono::steady_clock::now(); + } + + void postInterceptEvent(Event const &evt, bool processed) { + auto now = std::chrono::steady_clock::now(); + auto processingTime = now - _startProcessingTimestamp; + fmt::print("Processing of event type {} took {} ns", evt.name, std::chrono::duration_cast(processingTime).count()); + } + +private: + EventInterceptorRegistration _interceptorRegistration{}; + std::chrono::time_point _startProcessingTimestamp{}; +}; +``` diff --git a/examples/event_statistics_example/main.cpp b/examples/event_statistics_example/main.cpp index 5edef0d0..2ca7d79b 100644 --- a/examples/event_statistics_example/main.cpp +++ b/examples/event_statistics_example/main.cpp @@ -4,6 +4,7 @@ #include #include "UsingStatisticsService.h" +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/http_example/README.md b/examples/http_example/README.md new file mode 100644 index 00000000..4522bf79 --- /dev/null +++ b/examples/http_example/README.md @@ -0,0 +1,20 @@ +# HTTP Example + +This example starts up an HTTP server and an HTTP client that connects to said server. The client sends a JSON message to which the server responds and then quits. + +This demonstrates the following concepts: +* Defining and using serializers +* How to set up an HTTP server +* How to set up an HTTP client using the `ClientFactory` +* Using the advanced parameter `Spinlock` in the `MultimapQueue`. + +## Command Line Arguments + +```shell +-a, --address, "Address to bind to, e.g. 127.0.0.1" +-v, --verbosity, "Increase logging for each -v" +-t, --threads, "Number of threads to use for I/O, default: 1" +-p, --spinlock, "Spinlock 10ms before going to sleep, improves latency in high workload cases at the expense of CPU usage" +-s, --silent, "No output" +``` + diff --git a/examples/http_example/main.cpp b/examples/http_example/main.cpp index 1c95d213..2aee9326 100644 --- a/examples/http_example/main.cpp +++ b/examples/http_example/main.cpp @@ -9,6 +9,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #include @@ -82,10 +83,15 @@ int main(int argc, char *argv[]) { } else { dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(level)}}); } + // Create the JSON serializer for the TestMsg class dm.createServiceManager>(); + // Create the Event Thread for Boost.ASIO dm.createServiceManager(Properties{{"Threads", Ichor::make_any(threads)}}); + // Create the HTTP server binding to the given address dm.createServiceManager(Properties{{"Address", Ichor::make_any(address)}, {"Port", Ichor::make_any(static_cast(8001))}}); + // Setup a factory which creates HTTP clients for every class requesting an IHttpConnectionService dm.createServiceManager>(); + // Create the class that we defined in this example, to setup a /test endpoint in the server, to request (and therefore create) an HTTP client and send a message to the /test endpoint using the serializer for TestMsg dm.createServiceManager(Properties{{"Address", Ichor::make_any(address)}, {"Port", Ichor::make_any(static_cast(8001))}}); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); diff --git a/examples/http_ping_pong/ping.cpp b/examples/http_ping_pong/ping.cpp index ccd2a92b..e9c5f237 100644 --- a/examples/http_ping_pong/ping.cpp +++ b/examples/http_ping_pong/ping.cpp @@ -10,6 +10,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #include diff --git a/examples/http_ping_pong/pong.cpp b/examples/http_ping_pong/pong.cpp index 90f3ac02..dd689dde 100644 --- a/examples/http_ping_pong/pong.cpp +++ b/examples/http_ping_pong/pong.cpp @@ -8,6 +8,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #include diff --git a/examples/minimal_example/README.md b/examples/minimal_example/README.md new file mode 100644 index 00000000..30e88a0c --- /dev/null +++ b/examples/minimal_example/README.md @@ -0,0 +1,9 @@ +# Minimal Example + +This example shows how to set up and quit an Ichor program + +This demonstrates the following concepts: +* How to create the Queue and DependencyManager +* How to register your own services +* How to use the Timer subsystem +* How to quit the program from within the event loop diff --git a/examples/multithreaded_example/README.md b/examples/multithreaded_example/README.md new file mode 100644 index 00000000..271f4f69 --- /dev/null +++ b/examples/multithreaded_example/README.md @@ -0,0 +1,8 @@ +# Multithreaded Example + +This example shows how to use multiple DependencyManagers on multiple threads and how to communicate safely between the two. + +This demonstrates the following concepts: +* Creating multiple queues and DependencyManagers +* How to use the `CommunicationChannel` to communicate between different queues +* How to tell a different DependencyManager/queue to quit diff --git a/examples/multithreaded_example/main.cpp b/examples/multithreaded_example/main.cpp index b86c1217..2a7cca67 100644 --- a/examples/multithreaded_example/main.cpp +++ b/examples/multithreaded_example/main.cpp @@ -3,6 +3,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/optional_dependency_example/README.md b/examples/optional_dependency_example/README.md new file mode 100644 index 00000000..e1208983 --- /dev/null +++ b/examples/optional_dependency_example/README.md @@ -0,0 +1,8 @@ +# Optional Dependency Example + +This example shows how to use optional dependencies, including multiple of the same type. + +This demonstrates the following concepts: +* Requesting optional dependencies, separate from the lifecycle of the requesting service + * Normally, a service does not start until all of its required dependencies are met. Optional dependencies can come and go without starting/stopping the requesting service +* How to handle multiple dependencies of the same type diff --git a/examples/optional_dependency_example/main.cpp b/examples/optional_dependency_example/main.cpp index 348f1c7c..97eb1510 100644 --- a/examples/optional_dependency_example/main.cpp +++ b/examples/optional_dependency_example/main.cpp @@ -3,6 +3,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/realtime_example/README.md b/examples/realtime_example/README.md new file mode 100644 index 00000000..ce752a98 --- /dev/null +++ b/examples/realtime_example/README.md @@ -0,0 +1,3 @@ +# Realtime Example + +This example shows how Ichor behaves in a more real-time environment. It also includes code for Linux to increase the chance of meeting execution deadlines. diff --git a/examples/serializer_example/README.md b/examples/serializer_example/README.md new file mode 100644 index 00000000..772397f1 --- /dev/null +++ b/examples/serializer_example/README.md @@ -0,0 +1,25 @@ +# Serializer Example + +This example shows how to create and use a JSON serializer. The serializer itself is located in the [common](../common) directory. + +To implement your own, create a class that inherits from an `ISerializer` where `T` is the class/struct you want to serialize. + +This then forces you to implement two functions: +```c++ +class MyMsg { + int id; +}; + +class MySerializer final : public ISerializer { +public: + std::vector serialize(MyMsg const &msg) final { + // return a vector containing the binary format of your serialization + return std::vector{}; + } + + std::optional deserialize(std::vector &&stream) final { + // deserialize and return a MyMsg with the right id + return MyMsg{}; + } +}; +``` diff --git a/examples/serializer_example/main.cpp b/examples/serializer_example/main.cpp index 035ee9b2..078a6c4e 100644 --- a/examples/serializer_example/main.cpp +++ b/examples/serializer_example/main.cpp @@ -4,6 +4,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/tcp_example/README.md b/examples/tcp_example/README.md new file mode 100644 index 00000000..7af6b2f1 --- /dev/null +++ b/examples/tcp_example/README.md @@ -0,0 +1,3 @@ +# TCP Example + +This example shows how to create a TCP host and TCP client. diff --git a/examples/tcp_example/main.cpp b/examples/tcp_example/main.cpp index 7a6c063a..6dcdd46c 100644 --- a/examples/tcp_example/main.cpp +++ b/examples/tcp_example/main.cpp @@ -7,6 +7,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/timer_example/README.md b/examples/timer_example/README.md new file mode 100644 index 00000000..79471a5d --- /dev/null +++ b/examples/timer_example/README.md @@ -0,0 +1,3 @@ +# Timer Example + +This example shows how to use the Timer subsystem to schedule periodic tasks or execute tasks in the future. diff --git a/examples/timer_example/main.cpp b/examples/timer_example/main.cpp index 8081fef2..18d11841 100644 --- a/examples/timer_example/main.cpp +++ b/examples/timer_example/main.cpp @@ -3,6 +3,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/tracker_example/README.md b/examples/tracker_example/README.md new file mode 100644 index 00000000..c122525d --- /dev/null +++ b/examples/tracker_example/README.md @@ -0,0 +1,12 @@ +# Tracker Example + +This example shows how to 'track' dependency requests. Specifically, this is most commonly used to create factories. + +When a service is created normally, that dependency is considered a globally available dependency. That is, every service requesting it, will get a handle to the very same dependency. In the case of loggers, it is usually desired to set the logging level on a more granular level, which can be solved by creating a specific logger for each individual request. + + + + +This demonstrates the following concepts: +* Fulfilling a dependency per-request, instead of globally +* How to use the advanced `filter` feature to create a per-request dependency diff --git a/examples/tracker_example/TrackerService.h b/examples/tracker_example/TrackerService.h index 37c8fac3..28d7e0c2 100644 --- a/examples/tracker_example/TrackerService.h +++ b/examples/tracker_example/TrackerService.h @@ -7,10 +7,13 @@ using namespace Ichor; +// custom filter logic, to ensure a created RuntimeCreatedService only gets inserted into a `TestService` if the scope matches. class ScopeFilterEntry final { public: explicit ScopeFilterEntry(std::string _scope) : scope(std::move(_scope)) {} + // The `manager` here is the Ichor-specific abstraction that encapsulates the requesting service. In this case, it would be the Manager for `TestService`. + // The manager contains ways to get the assigned properties (which we use here to retrieve the scope), but also a list of dependencies the `TestService` requested. [[nodiscard]] bool matches(ILifecycleManager const &manager) const noexcept { auto const scopeProp = manager.getProperties().find("scope"); @@ -31,7 +34,9 @@ class TrackerService final { } private: + // a service has been created and has requested an IRuntimeCreatedService void handleDependencyRequest(AlwaysNull, DependencyRequestEvent const &evt) { + // What to do when the requesting service has no properties? In this case, we don't create anything, probably preventing the service from being started. if(!evt.properties.has_value()) { ICHOR_LOG_ERROR(_logger, "missing properties"); return; @@ -48,16 +53,20 @@ class TrackerService final { ICHOR_LOG_INFO(_logger, "Tracked IRuntimeCreatedService request for scope {}", scope); + // See if we have already created a `RuntimeCreatedService` for this particular scope auto runtimeService = _scopedRuntimeServices.find(scope); if(runtimeService == end(_scopedRuntimeServices)) { auto newProps = *evt.properties.value(); + // `Filter` is a magic keyword that Ichor uses to determine if this service is global or if Ichor should use its filtering logic. + // In this case, we tell Ichor to only insert this service if the requesting service has a matching scope newProps.emplace("Filter", Ichor::make_any(Filter{ScopeFilterEntry{scope}})); _scopedRuntimeServices.emplace(scope, GetThreadLocalManager().createServiceManager(std::move(newProps))); } } + // a service has been created but now has to be destroyed and requested an IRuntimeCreatedService void handleDependencyUndoRequest(AlwaysNull, DependencyUndoRequestEvent const &evt) { auto scopeProp = evt.properties.find("scope"); @@ -70,6 +79,8 @@ class TrackerService final { ICHOR_LOG_INFO(_logger, "Tracked IRuntimeCreatedService undo request for scope {}", scope); + // This logic does not account for multiple services using the same scope + // That is easily fixed by using a reference counter in the container, but is outside the scope of this example (pun intended) auto service = _scopedRuntimeServices.find(scope); if(service != end(_scopedRuntimeServices)) { // TODO: This only works for synchronous start/stop loggers, maybe turn into async and await a stop before removing? diff --git a/examples/tracker_example/main.cpp b/examples/tracker_example/main.cpp index dea0b552..91ebfe45 100644 --- a/examples/tracker_example/main.cpp +++ b/examples/tracker_example/main.cpp @@ -4,6 +4,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger diff --git a/examples/websocket_example/README.md b/examples/websocket_example/README.md new file mode 100644 index 00000000..678cf89d --- /dev/null +++ b/examples/websocket_example/README.md @@ -0,0 +1,20 @@ +# Websocket Example + +This example starts up a websocket server and a websocket client that connects to said server. The client sends a JSON message to which the server responds and then quits. + +This demonstrates the following concepts: +* Defining and using serializers +* How to set up a websocket server +* How to set up a websocket client using the `ClientFactory` +* Using the advanced parameter `Spinlock` in the `MultimapQueue`. + +## Command Line Arguments + +```shell +-a, --address, "Address to bind to, e.g. 127.0.0.1" +-v, --verbosity, "Increase logging for each -v" +-t, --threads, "Number of threads to use for I/O, default: 1" +-p, --spinlock, "Spinlock 10ms before going to sleep, improves latency in high workload cases at the expense of CPU usage" +-s, --silent, "No output" +``` + diff --git a/examples/websocket_example/main.cpp b/examples/websocket_example/main.cpp index 020a47d2..7a021308 100644 --- a/examples/websocket_example/main.cpp +++ b/examples/websocket_example/main.cpp @@ -9,6 +9,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #include diff --git a/examples/yielding_timer_example/README.md b/examples/yielding_timer_example/README.md new file mode 100644 index 00000000..54d37297 --- /dev/null +++ b/examples/yielding_timer_example/README.md @@ -0,0 +1,3 @@ +# Yielding Timer Example + +This example is a more advanced use-case in comparison to the [Timer Example](../timer_example). This demonstrates how to break up long-running tasks to ensure other, higher priority tasks can still be processed without blocking the queue for the entire time. This is also called cooperative scheduling. diff --git a/examples/yielding_timer_example/main.cpp b/examples/yielding_timer_example/main.cpp index aab33a57..297281da 100644 --- a/examples/yielding_timer_example/main.cpp +++ b/examples/yielding_timer_example/main.cpp @@ -3,6 +3,7 @@ #include #include +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. #ifdef ICHOR_USE_SPDLOG #include #define LOGGER_TYPE SpdlogLogger