Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,28 +245,19 @@ jobs:
if: runner.os == 'Linux'
uses: awalsh128/cache-apt-pkgs-action@9f7a885e33ff6f8166b600e3e74a981ec11b92c3
with:
packages: pkg-config zlib1g-dev libssl-dev libcrypt-dev libcurl4-openssl-dev libgtk-3-dev libepoxy-dev libdbus-1-dev libfontconfig-dev ninja-build libpulse-dev
packages: pkg-config zlib1g-dev libssl-dev libcrypt-dev libcurl4-openssl-dev libgtk-3-dev libepoxy-dev libdbus-1-dev libfontconfig-dev ninja-build libpulse-dev clang
execute_install_scripts: true
version: 4.0

- name: Clang Toolchain Installation for Linux
if: matrix.compiler == 'Clang' && runner.os == 'Linux'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
echo "CC=clang-20" >> $GITHUB_ENV
echo "CXX=clang++-20" >> $GITHUB_ENV

- name: Install MacOS dependencies
if: runner.os == 'macOS'
run: brew install cmake ninja llvm@18
run: brew install cmake ninja llvm@20

- name: Setup Clang 18 for macOS
- name: Setup Clang for macOS
if: matrix.compiler == 'Clang' && runner.os == 'macOS'
run: |
echo "CC=$(brew --prefix llvm@18)/bin/clang " >> $GITHUB_ENV
echo "CXX=$(brew --prefix llvm@18)/bin/clang++ " >> $GITHUB_ENV
echo "CC=$(brew --prefix llvm@20)/bin/clang " >> $GITHUB_ENV
echo "CXX=$(brew --prefix llvm@20)/bin/clang++ " >> $GITHUB_ENV

- name: Setup AppleClang for macOS
if: matrix.compiler == 'Default' && runner.os == 'macOS'
Expand Down
10 changes: 9 additions & 1 deletion aui.core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
cmake_minimum_required(VERSION 3.16)

option(AUI_CATCH_UNHANDLED "Catch segfault" ON)
option(AUI_COROUTINES "Use C++20 coroutines" OFF)

set(_use_coroutines ON)
if (AUI_COMPILER_CLANG)
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 21.0.0) # compiler crashes
set(_use_coroutines OFF)
endif ()
endif()

option(AUI_COROUTINES "Use C++20 coroutines" ${_use_coroutines})
option(AUI_ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(AUI_ENABLE_DEATH_TESTS "Enable GTest death tests" ON)

Expand Down
5 changes: 5 additions & 0 deletions aui.core/src/AUI/Common/AChar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ AStaticVector<char, 4> AChar::toUtf8() const noexcept {
}
return {}; // Invalid Unicode code point
}

std::string AChar::toString() const {
auto tmp = toUtf8();
return std::string(tmp.begin(), tmp.end());
}
2 changes: 2 additions & 0 deletions aui.core/src/AUI/Common/AChar.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#pragma once

#include <cstddef>
#include <string>

template <typename StoredType, std::size_t MaxSize>
class AStaticVector;
Expand Down Expand Up @@ -84,6 +85,7 @@ class AChar {
}

AStaticVector<char, 4> toUtf8() const noexcept;
std::string toString() const;

constexpr char32_t codepoint() const noexcept {
return mValue;
Expand Down
8 changes: 6 additions & 2 deletions aui.core/src/AUI/Common/AString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,12 @@ void AString::erase(size_t u_pos, size_t u_count) {
erase(begin() + u_pos, begin() + u_pos + u_count);
}

AStringVector AString::split(AChar c) const {
return view().split(c);
AStringVector AString::split(AChar separator) const {
return view().split(separator);
}

AStringVector AString::split(AStringView separator) const {
return view().split(separator);
}

// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,cppcoreguidelines-pro-bounds-pointer-arithmetic)
3 changes: 2 additions & 1 deletion aui.core/src/AUI/Common/AString.h
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ class API_AUI_CORE AString: public std::string {
return view().lowercase();
}

AStringVector split(AChar c) const;
AStringVector split(AChar separator) const;
AStringVector split(AStringView separator) const;

AString& replaceAll(AChar from, AChar to);

Expand Down
17 changes: 9 additions & 8 deletions aui.core/src/AUI/Common/AStringView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1045,28 +1045,29 @@ bool AStringView::toBool() const {
(d[3] == 'e' || d[3] == 'E');
}

AStringVector AStringView::split(AChar c) const {
AStringVector AStringView::split(AStringView separator) const {
if (empty()) {
return {};
}
auto utf8c = c.toUtf8();
if (utf8c.empty()) return {};
std::string separator_utf8(utf8c.begin(), utf8c.end());
AStringVector result;
result.reserve(length() / 10);
for (size_type s = 0;;) {
auto next = super::find(separator_utf8, s);
auto next = super::find(separator.bytes(), s);
if (next == npos) {
result << substr(s);
result << super::substr(s);
break;
}

result << substr(s, next - s);
s = next + separator_utf8.length();
result << super::substr(s, next - s);
s = next + separator.bytes().length();
}
return result;
}

AStringVector AStringView::split(AChar separator) const {
return split(AStringView(separator.toString()));
}

AString AStringView::removedAll(AChar c) {
AString copy{*this};
copy.removeAll(c);
Expand Down
3 changes: 2 additions & 1 deletion aui.core/src/AUI/Common/AStringView.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ class API_AUI_CORE AStringView: public std::string_view {

AString removedAll(AChar c);

AStringVector split(AChar c) const;
AStringVector split(AStringView separator) const;
AStringVector split(AChar separator) const;

constexpr iterator begin() const noexcept {
return AUtf8ConstIterator(data(), data(), data() + size(), 0);
Expand Down
9 changes: 9 additions & 0 deletions aui.core/src/AUI/Platform/unix/UnixIoThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ void UnixIoThread::unregisterCallback(int fd) noexcept {
});
}

AFuture<ABitField<UnixPollEvent>> UnixIoThread::waitForEvent(int fd, ABitField<UnixPollEvent> flags) {
AFuture<ABitField<UnixPollEvent>> future;
registerCallback(fd, flags, [this, future, fd](ABitField<UnixPollEvent> event) {
unregisterCallback(fd);
future.supplyValue(event);
});
return future;
}

UnixIoThread::UnixIoThread() noexcept: mThread(_new<AThread>([&] {
AThread::setName("AUI IO");
UnixIoEventLoop loop(*this);
Expand Down
21 changes: 21 additions & 0 deletions aui.core/src/AUI/Platform/unix/UnixIoThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ class API_AUI_CORE UnixIoThread {
return inst().mThread;
}

/**
* @brief A simplified version of registerCallback/unregisterCallback for waiting events on file descriptor.
* @param fd file descriptor to watch on.
* @param flags which events to listen
* @return AFuture with bitfield on which events was triggerred
* @details
* ```cpp
* AAsyncHolder async;
* async << []() -> AFuture<> {
* int signalFd = signalfd(-1, &procMask, SFD_CLOEXEC);
* for (;;) {
* co_await UnixIoThread::inst().waitForEvent(signalFd, UnixPollEvent::IN);
* read(signalFd, ...);
* }
* close(signalFd);
* };
* ```
*/
[[nodiscard]]
AFuture<ABitField<UnixPollEvent>> waitForEvent(int fd, ABitField<UnixPollEvent> flags);

private:
friend class UnixIoEventLoop;
_<AThread> mThread;
Expand Down
26 changes: 26 additions & 0 deletions aui.core/src/AUI/Thread/AAsyncHolder.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ class AAsyncHolder {
return f.inner().get() == impl;
});
});

future.onError([this, impl](const AException& e) {
std::unique_lock lock(mSync);
mOnException(e);
mFutureSet.removeIf([&](const AFuture<>& f) {
return f.inner().get() == impl;
});
});
} else {
auto uniquePtr = std::make_unique<Future<T>>(future);
auto it = mCustomTypeFutures.insert(mCustomTypeFutures.end(), std::move(uniquePtr));
Expand All @@ -80,6 +88,14 @@ class AAsyncHolder {
AUI_ASSERT(!mCustomTypeFutures.empty());
mCustomTypeFutures.erase(it);
});

future.onError([this, it](const AException& result) {
AUI_ASSERTX(!mDead, "you have concurrency issues");
std::unique_lock lock(mSync);
mOnException(result);
AUI_ASSERT(!mCustomTypeFutures.empty());
mCustomTypeFutures.erase(it);
});
}
return *this;
}
Expand Down Expand Up @@ -111,6 +127,13 @@ class AAsyncHolder {
}
}

[[nodiscard]] std::function<void(const AException&)> getOnException() const { return mOnException; }

void setOnException(std::function<void(const AException&)> callback) {
std::unique_lock lock(mSync);
mOnException = std::move(callback);
}

private:
struct IFuture {
virtual ~IFuture() = default;
Expand Down Expand Up @@ -150,4 +173,7 @@ class AAsyncHolder {
mutable AMutex mSync;
AFutureSet<> mFutureSet;
std::list<_unique<IFuture>> mCustomTypeFutures;
std::function<void(const AException&)> mOnException = [](const AException& e) {
ALogger::err("AAsyncHolder") << "Unhandled exception in AFuture: " << e;
};
};
60 changes: 47 additions & 13 deletions aui.core/src/AUI/Thread/AFuture.h
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ namespace aui::impl::future {
* AFuture<int> longOperation();
* AFuture<int> myFunction() {
* int resultOfLongOperation = co_await longOperation();
* return resultOfLongOperation + 1;
* co_return resultOfLongOperation + 1;
* }
* ```
*
Expand Down Expand Up @@ -621,7 +621,7 @@ namespace aui::impl::future {
* AFuture may execute the task (if not default-constructed) on the caller thread instead of waiting. See AFuture::wait
* for details.
*/
template<typename T = void>
template<typename T>
class AFuture final: public aui::impl::future::Future<T> {
private:
using super = typename aui::impl::future::Future<T>;
Expand Down Expand Up @@ -950,6 +950,7 @@ struct aui::impl::future::Future<Value>::CoPromiseType {
{
return std::suspend_never{};
}

auto unhandled_exception() const noexcept {
future.supplyException();
}
Expand All @@ -963,6 +964,32 @@ struct aui::impl::future::Future<Value>::CoPromiseType {
}
};

template<>
struct aui::impl::future::Future<void>::CoPromiseType {
AFuture<void> future;
auto initial_suspend() const noexcept
{
return std::suspend_never{};
}

auto final_suspend() const noexcept
{
return std::suspend_never{};
}

auto unhandled_exception() const noexcept {
future.supplyException();
}

const AFuture<void>& get_return_object() const noexcept {
return future;
}

void return_void() const noexcept {
future.supplyValue();
}
};

template<typename T>
auto operator co_await(AFuture<T> future) {
struct Awaitable {
Expand All @@ -972,20 +999,27 @@ auto operator co_await(AFuture<T> future) {
return future.hasResult();
}

T await_resume() {
return *future;
auto await_resume() {
if constexpr (std::is_same_v<T, void>) {
*future;
} else {
return std::move(*future);
}
}


void await_suspend(std::coroutine_handle<> h)
void await_suspend(std::coroutine_handle<> handle)
{
future.onSuccess([h](const int&) {
h.resume();
});

future.onError([h](const AException&) {
h.resume();
});
// onSuccess and onError are called by supplyValue which might have been called on a different thread.
// if we keep this logic for coroutines, the callstack will grow very fast and inconsistent.
// for co_await, let's keep the caller thread.
// if user wants to magically switch threads using co_await, they'll figure out how to shoot their knee.
auto callback = [handle = std::move(handle), callerThread = AThread::current()](const auto&...) {
callerThread->enqueue([handle = std::move(handle)] {
handle.resume();
});
};
future.onSuccess(callback);
future.onError(std::move(callback));
}
};

Expand Down
10 changes: 10 additions & 0 deletions aui.core/src/AUI/Thread/AThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "AUI/Thread/AFuture.h"
#include "AUI/Thread/AMutexWrapper.h"
#include "IEventLoop.h"
#include "AUI/Common/ATimer.h"

#include <AUI/Thread/AConditionVariable.h>
#include <cstdint>
#include <functional>
Expand Down Expand Up @@ -289,3 +291,11 @@ const _<AAbstractThread>& AThread::main() noexcept {
static auto main = current(); // initialized by AUI_ENTRY.
return main;
}

AFuture<> AThread::asyncSleep(std::chrono::milliseconds duration) {
AFuture<> future;
ATimer::scheduler().enqueue(duration, [future] {
future.supplyValue();
});
return future;
}
19 changes: 19 additions & 0 deletions aui.core/src/AUI/Thread/AThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class IEventLoop;
class AString;
class AConditionVariable;

template<typename T = void>
class AFuture;

/**
* @brief Represents an abstract thread which might be not created with AThread.
* @ingroup core
Expand Down Expand Up @@ -224,9 +227,25 @@ class API_AUI_CORE AThread : public AAbstractThread, public AObject {
* Most operation systems guarantee that elasped time will be greater than specified.
* <code>AThread::interrupt()</code> is supported.
* @param duration sleep duration.
* @details
* Blocking sleep.
*/
static void sleep(std::chrono::milliseconds duration);

/**
* @brief Sleep for specified duration.
* Most operation systems guarantee that elasped time will be greater than specified.
* <code>AThread::interrupt()</code> is supported.
* @param duration sleep duration.
* @details
* Async sleep.
*
* ```cpp
* co_await AThread::asyncSleep(1s);
* ```
*/
static AFuture<> asyncSleep(std::chrono::milliseconds duration);

/**
* @return current thread.
*/
Expand Down
Loading