diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c0bef0..302b42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,16 @@ project( find_package(spdlog REQUIRED) find_package(avcpp REQUIRED) + # ############################################################################## +add_library(mosh-me-lib STATIC + ${PROJECT_SOURCE_DIR}/src/pipeline.cpp +) +target_link_libraries(mosh-me-lib PUBLIC avcpp::avcpp spdlog::spdlog) +target_compile_features(mosh-me-lib PUBLIC cxx_std_23) +target_include_directories(mosh-me-lib PUBLIC ${PROJECT_SOURCE_DIR}/include) + add_executable(mosh-me ${PROJECT_SOURCE_DIR}/app/mosh-me.cpp ) @@ -30,7 +38,19 @@ install(TARGETS mosh-me mk-moshable) ################################################################################ +if (BUILD_TESTING) + find_package(Catch2 REQUIRED) + add_executable(mosh-me_test + ${PROJECT_SOURCE_DIR}/test/pipeline.cpp + ) + target_link_libraries(mosh-me_test mosh-me-lib Catch2::Catch2WithMain) +endif() + +################################################################################ + if(CMAKE_EXPORT_COMPILE_COMMANDS) set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}) endif() + + diff --git a/flake.nix b/flake.nix index 314e6fe..e9957bc 100644 --- a/flake.nix +++ b/flake.nix @@ -41,6 +41,7 @@ export CMAKE_EXPORT_COMPILE_COMMANDS=ON export CMAKE_BUILD_TYPE=Debug export CMAKE_GENERATOR=Ninja + export BUILD_TESTING=ON ''; packages = [ pkgs.clang-tools diff --git a/include/mosh-me/pipeline.hpp b/include/mosh-me/pipeline.hpp new file mode 100644 index 0000000..02be97f --- /dev/null +++ b/include/mosh-me/pipeline.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include + +namespace mosh_me { + +template +concept AttachableTo = requires { + typename A::output_type; + typename B::input_type; +} && std::convertible_to; + +template class PipeOutput { +public: + virtual std::optional pull() noexcept = 0; +}; + +template class PipeInput { +private: + std::shared_ptr> input_; + +protected: + std::optional fetchInput() { return input_->pull(); } + +public: + void linkInput(std::shared_ptr> input) { input_ = input; }; +}; + +template +class PipeWorker : public PipeOutput, public PipeInput {}; + +template class PipeSource { +public: + std::shared_ptr> source_; + + PipeSource(std::shared_ptr> p) : source_(p) {} + + std::optional pull() { + if (source_) { + return source_->pull(); + } else { + return std::nullopt; + } + } +}; + +template class PipeLink { +public: + std::shared_ptr> input_; + std::shared_ptr> output_; + + PipeLink(std::shared_ptr> i, + std::shared_ptr> o) + : input_(i), output_(o) {} + PipeLink(std::shared_ptr> p) + : input_(p), output_(p) {} + + template + PipeLink operator<<(PipeLink &&other) { + input_->linkInput(std::move(other.output_)); + return PipeLink(std::move(other.input_), std::move(output_)); + } + + template PipeSource operator<<(PipeSource &&other) { + input_->linkInput(std::move(other.source_)); + return PipeSource(std::move(output_)); + } +}; + +template class AsPipeSource { +public: + template static PipeSource link(Args &&...args) { + return PipeSource( + std::make_shared(std::forward(args)...)); + } +}; + +template class AsPipeLink { +public: + template static PipeLink link(Args &&...args) { + return PipeLink( + std::make_shared(std::forward(args)...)); + } +}; + +} // namespace mosh_me diff --git a/nix/mosh-me.nix b/nix/mosh-me.nix index d5976dd..7a48a08 100644 --- a/nix/mosh-me.nix +++ b/nix/mosh-me.nix @@ -6,12 +6,13 @@ stdenv, pkg-config, avcpp, + catch2_3, }: stdenv.mkDerivation (finalAttrs: { name = "mosh-me"; src = ../.; nativeBuildInputs = [cmake ninja pkg-config]; - buildInputs = [spdlog avcpp]; + buildInputs = [spdlog avcpp] ++ lib.optional finalAttrs.doCheck catch2_3; cmakeFlags = [ (lib.cmakeBool "BUILD_TESTING" finalAttrs.doCheck) ]; diff --git a/src/pipeline.cpp b/src/pipeline.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/pipeline.cpp b/test/pipeline.cpp new file mode 100644 index 0000000..0d2d9cf --- /dev/null +++ b/test/pipeline.cpp @@ -0,0 +1,75 @@ +#include "mosh-me/pipeline.hpp" +#include +#include +#include + +using namespace mosh_me; + +class CountProducer : public PipeOutput, + public AsPipeSource { +private: + int counter_ = 0; + +public: + std::optional pull() noexcept override { + if (auto i = counter_++; i < 10) { + return i; + } else { + return std::nullopt; + } + } +}; + +class Doubler : public PipeWorker, + public AsPipeLink { +public: + std::optional pull() noexcept override { + return fetchInput().and_then( + [](int x) { return std::make_optional(x * 2); }); + } +}; + +class Multiplier : public PipeWorker, + public AsPipeLink { + int f_; + +public: + Multiplier(int f) : f_(f) {} + std::optional pull() noexcept override { + return fetchInput().and_then( + [this](int x) { return std::make_optional(f_ * x); }); + } +}; + +class Sum2 : public PipeWorker, public AsPipeLink { +private: + std::optional last_; + +public: + std::optional pull() noexcept override { + if (auto x = fetchInput(); x) { + if (last_) { + auto sum = *last_ + *x; + last_ = std::nullopt; + return sum; + } else { + last_ = x; + return pull(); + } + } + return std::nullopt; + } +}; + +TEST_CASE("Pipeline") { + auto pipe = Sum2::link() << Doubler::link() << Multiplier::link(10) + << CountProducer::link(); + + auto count = 0; + while (auto s = pipe.pull()) { + REQUIRE(*s == ((count * 2) * 20) + ((count * 2 + 1) * 20)); + count++; + } + + REQUIRE(count == 5); +}