Skip to content

Commit 4622b35

Browse files
committed
new stuff
added valgrind leak tests to linux workflow
1 parent c2773e8 commit 4622b35

15 files changed

Lines changed: 626 additions & 110 deletions

.github/workflows/linux.yaml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,40 @@ jobs:
5858
cmake --build build
5959
- name: Run tests
6060
run: |
61-
./build/oryx-crt-cpp_tests --success
61+
./build/oryx-crt-cpp_tests --success
62+
linux-valgrind:
63+
name: "Valgrind leak checks"
64+
runs-on: ubuntu-latest
65+
steps:
66+
- name: Setup
67+
uses: actions/checkout@v4
68+
run: |
69+
apt-get update
70+
apt-get install -y valgrind
71+
72+
- name: Checkout
73+
uses: actions/checkout@v4
74+
with:
75+
submodules: recursive
76+
fetch-depth: 0
77+
- name: Export GitHub Actions cache environment variables
78+
uses: actions/github-script@v7
79+
with:
80+
script: |
81+
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
82+
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
83+
- name: Setup ccache
84+
uses: hendrikmuhs/ccache-action@v1
85+
with:
86+
key: "${{ github.job }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}"
87+
max-size: "2G"
88+
- name: Compile
89+
run: |
90+
sudo ln -s $(which ccache) /usr/local/bin/$CC
91+
sudo ln -s $(which ccache) /usr/local/bin/$CXX
92+
$CXX --version
93+
cmake -B build -G Ninja -DORYX_CRT_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
94+
cmake --build build
95+
- name: Run tests
96+
run: |
97+
valgrind --leak-check=full --error-exitcode=1 ./build/oryx-crt-cpp_tests

CMakeLists.txt

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ else()
1919
add_library(${PROJECT_NAME} STATIC)
2020
endif()
2121

22+
add_library("${PROJECT_NAME}::${PROJECT_NAME}" ALIAS ${PROJECT_NAME})
23+
2224
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
2325
target_compile_options(${PROJECT_NAME}
2426
PRIVATE
@@ -59,21 +61,9 @@ target_sources(${PROJECT_NAME}
5961

6062

6163
if(ORYX_CRT_BUILD_TESTS)
64+
file(GLOB_RECURSE TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp")
6265
set(test_exe ${PROJECT_NAME}_tests)
63-
add_executable(${test_exe}
64-
tests/main.cpp
65-
tests/argparse_test.cpp
66-
tests/stopwatch_test.cpp
67-
tests/error_test.cpp
68-
tests/string_split_test.cpp
69-
tests/callback_list_test.cpp
70-
tests/from_chars_test.cpp
71-
tests/is_one_off_test.cpp
72-
tests/unique_file_ptr_test.cpp
73-
tests/error_group_test.cpp
74-
tests/traits_test.cpp
75-
)
76-
66+
add_executable(${test_exe} ${TEST_SOURCES})
7767
target_link_libraries(${test_exe} PRIVATE ${PROJECT_NAME})
7868

7969
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")

include/oryx/callback_list.hpp

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ template <class... Args>
1717
class CallbackList {
1818
public:
1919
using ID = uint64_t;
20+
21+
static constexpr ID kIDMax = std::numeric_limits<ID>::max();
2022
/**
2123
* @brief Handle to subscription. Can be used to unsubscribe from notifications.
2224
*/
2325
class Handle {
2426
public:
2527
Handle()
26-
: id_(0) {}
28+
: id_(kIDMax) {}
2729

28-
auto IsValid() const -> bool { return id_ != 0; }
30+
auto IsValid() const -> bool { return id_ != kIDMax; }
2931
auto id() const { return id_; }
3032

3133
private:
@@ -34,58 +36,61 @@ class CallbackList {
3436
explicit Handle(ID id)
3537
: id_(id) {}
3638

39+
void Reset() { id_ = kIDMax; }
40+
3741
ID id_;
3842
};
3943

4044
using Callback = std::function<void(Args...)>;
4145

4246
CallbackList() = default;
4347

44-
auto Subscribe(const Callback& cb) -> Handle {
45-
auto handle = CreateHandle();
46-
subs_.wlock()->emplace_back(handle.id(), cb);
47-
return handle;
48-
}
49-
5048
auto Subscribe(Callback&& cb) -> Handle {
51-
auto handle = CreateHandle();
52-
subs_.wlock()->emplace_back(handle.id(), std::forward<Callback>(cb));
53-
return handle;
49+
ID id = id_.fetch_add(1, std::memory_order_relaxed);
50+
subs_.Apply([&](auto& subs) mutable { subs.emplace_back(id, std::forward<Callback>(cb)); });
51+
return Handle(id);
5452
}
5553

5654
void Unsubscribe(Handle& handle) {
5755
if (!handle.IsValid()) {
5856
return;
5957
}
6058

61-
auto subs_ptr = subs_.wlock();
62-
auto it = std::ranges::find_if(*subs_ptr, [hid = handle.id()](ID id) { return id == hid; }, &Subscriber::id);
63-
if (it != subs_ptr->end()) {
64-
subs_ptr->erase(it);
65-
handle = {};
66-
}
59+
subs_.Apply([&handle](auto& subs) {
60+
auto it = std::ranges::find(subs, handle.id_, &Subscriber::id);
61+
if (it != subs.end()) {
62+
subs.erase(it);
63+
handle.Reset();
64+
}
65+
});
6766
}
6867

6968
void Notify(const Args&... args) const {
70-
for (auto& sub : *subs_.rlock()) {
71-
sub.cb(args...);
72-
}
69+
subs_.Visit([&](auto& subs) {
70+
for (auto& sub : subs) sub.cb(args...);
71+
});
7372
}
7473

75-
void Clear() { subs_.wlock()->clear(); }
76-
auto IsEmpty() const { return subs_.rlock()->empty(); }
77-
auto Size() const { return subs_.rlock()->size(); }
74+
void Clear() {
75+
subs_.Apply([](auto& subs) { subs.clear(); });
76+
}
77+
78+
auto IsEmpty() const {
79+
return subs_.Visit([](auto& subs) { return subs.empty(); });
80+
}
81+
82+
auto Size() const {
83+
return subs_.Visit([](auto& subs) { return subs.size(); });
84+
}
7885

7986
private:
8087
struct Subscriber {
8188
ID id;
8289
Callback cb;
8390
};
8491

85-
auto CreateHandle() -> Handle { return Handle(id_.fetch_add(1, std::memory_order_relaxed)); }
86-
87-
std::atomic<ID> id_{1};
88-
synchronized<std::vector<Subscriber>> subs_{};
92+
std::atomic<ID> id_{};
93+
Synchronized<std::vector<Subscriber>> subs_{};
8994
};
9095

9196
} // namespace oryx

include/oryx/lazy_component.hpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#pragma once
2+
3+
#include <concepts>
4+
#include <functional>
5+
#include <memory>
6+
#include <mutex>
7+
#include <type_traits>
8+
#include <utility>
9+
#include <cassert>
10+
11+
namespace oryx {
12+
13+
template <class T>
14+
class LazyComponent {
15+
public:
16+
using FactoryContainer = std::move_only_function<T()>;
17+
18+
LazyComponent() = default;
19+
20+
LazyComponent()
21+
requires std::is_default_constructible_v<T>
22+
: factory_([] { return T(); }) {}
23+
24+
// Constructor that takes a creator callable
25+
template <typename Factory>
26+
requires std::invocable<Factory> && std::same_as<std::invoke_result_t<Factory>, T>
27+
explicit LazyComponent(Factory&& factory)
28+
: factory_(std::forward<Factory>(factory)) {}
29+
30+
auto operator*() -> T& { return *Get(); }
31+
auto operator*() const -> const T& { return *Get(); }
32+
auto operator->() -> T* { return Get(); }
33+
auto operator->() const -> const T* { return Get(); }
34+
35+
void Init() { InitOnce(); }
36+
37+
auto Get() -> T* {
38+
InitOnce();
39+
return value_.get();
40+
}
41+
42+
auto Get() const -> const T* {
43+
InitOnce();
44+
return value_.get();
45+
}
46+
47+
auto IsInitialized() const noexcept -> bool { return value_ != nullptr; }
48+
49+
private:
50+
void InitOnce() {
51+
assert(static_cast<bool>(factory_) && "LazyComponent requires valid factory callable!");
52+
std::call_once(created_flag_, [this] { value_ = std::make_unique<T>(std::invoke(factory_)); });
53+
}
54+
55+
std::once_flag created_flag_{};
56+
std::move_only_function<T()> factory_{};
57+
mutable std::unique_ptr<T> value_{};
58+
};
59+
60+
template <class Factory>
61+
LazyComponent(Factory) -> LazyComponent<std::invoke_result_t<Factory>>;
62+
63+
template <class Factory>
64+
auto MakeLazy(Factory&& factory) {
65+
return LazyComponent<std::invoke_result_t<Factory>>(std::forward<Factory>(factory));
66+
}
67+
68+
} // namespace oryx

0 commit comments

Comments
 (0)