Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ on:
jobs:
package_and_upload_all_check:
uses: libhal/ci/.github/workflows/package_and_upload_all.yml@5.x.y
with:
modules_support_needed: true
coroutine_support_needed: true
secrets: inherit
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
with:
version: ${{ github.ref_name }}
remote_url: https://libhal.jfrog.io/artifactory/api/conan/trunk-conan
modules_support_needed: true
coroutine_support_needed: true
secrets:
conan_remote_password: ${{ secrets.JFROG_LIBHAL_TRUNK_ID_TOKEN }}
conan_remote_user: ${{ secrets.JFROG_LIBHAL_TRUNK_ID_TOKEN_USER }}
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ install(

install(
EXPORT async_context_targets
FILE "libasync_context-config.cmake"
NAMESPACE libasync_context::
FILE "async_context-config.cmake"
DESTINATION "lib/cmake"
CXX_MODULES_DIRECTORY "cxx-modules"
EXPORT_PACKAGE_DEPENDENCIES
)

# ==============================================================================
Expand All @@ -63,6 +63,7 @@ find_package(ut REQUIRED)
if(CMAKE_CROSSCOMPILING)
message(STATUS "Cross compiling, skipping unit test execution")
else()
message(STATUS "Building unit tests!")
add_executable(async_unit_test)
# Add module files
target_sources(async_unit_test
Expand All @@ -75,7 +76,6 @@ else()
target_compile_options(async_unit_test PRIVATE -g)
target_link_libraries(async_unit_test PRIVATE async_context Boost::ut)

message(STATUS "Executing unit tests!")
add_custom_target(run_tests ALL DEPENDS async_unit_test COMMAND async_unit_test)
endif()

Expand Down
8 changes: 4 additions & 4 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class async_context_conan(ConanFile):
description = ("Implementation of C++20 coroutines targeting embedded system by eliminating the usage of the global heap and providing a 'context' which contains a coroutine stack frame and other useful utilities for scheduling.")
topics = ("async", "coroutines", "stack", "scheduling", "scheduler")
settings = "compiler", "build_type", "os", "arch"
exports_sources = "modules/*", "tests/*", "CMakeLists.txt", "LICENSE"
exports_sources = "modules/*", "tests/*", "CMakeLists.txt", "*.cmake.in", "LICENSE"
package_type = "static-library"
shared = False

Expand Down Expand Up @@ -84,12 +84,12 @@ def validate(self):
self._validate_compiler_version()

def build_requirements(self):
# Provides CMake, Ninja, & toolchain scripts for enabling modules
self.tool_requires("cmake-modules-toolchain/1.0.2")
self.tool_requires("cmake/[^4.0.0]")
self.tool_requires("ninja/[^1.3.0]")
self.test_requires("boost-ext-ut/2.3.1")

def requirements(self):
self.requires("strong_ptr/0.0.0")
self.requires("strong_ptr/0.0.2")

def layout(self):
cmake_layout(self)
Expand Down
30 changes: 14 additions & 16 deletions modules/async_context.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ module;
#include <chrono>
#include <coroutine>
#include <exception>
#include <functional>
#include <memory_resource>
#include <new>
#include <span>
Expand All @@ -32,14 +31,13 @@ export module async_context;

export import strong_ptr;

export namespace async::inline v0 {
namespace async::inline v0 {

using u8 = std::uint8_t;
using byte = std::uint8_t;
using usize = std::size_t;
export using u8 = std::uint8_t;
export using byte = std::uint8_t;
export using usize = std::size_t;

enum class blocked_by : u8
{
export enum class blocked_by : u8 {
/// Not blocked by anything, ready to run!
nothing = 0,

Expand All @@ -63,7 +61,7 @@ enum class blocked_by : u8
external = 4,
};

class context;
export class context;

/**
* @brief Thrown when an async::context runs out of stack memory
Expand All @@ -72,7 +70,7 @@ class context;
* cannot fit withint he context.
*
*/
struct bad_coroutine_alloc : std::bad_alloc
export struct bad_coroutine_alloc : std::bad_alloc
{
bad_coroutine_alloc(context const* p_violator)
: violator(p_violator)
Expand Down Expand Up @@ -117,7 +115,7 @@ using sleep_duration = std::chrono::nanoseconds;
* @brief
*
*/
class scheduler
export class scheduler
{
public:
/**
Expand Down Expand Up @@ -156,7 +154,7 @@ private:
std::variant<sleep_duration, context*> p_block_info) = 0;
};

class context
export class context
{
public:
static auto constexpr default_timeout = sleep_duration(0);
Expand Down Expand Up @@ -415,10 +413,10 @@ protected:
class context* m_context;
};

template<typename T>
export template<typename T>
class future;

template<typename T>
export template<typename T>
class future_promise_type : public promise_base
{
public:
Expand Down Expand Up @@ -505,7 +503,7 @@ private:
usize m_frame_size;
};

template<>
export template<>
class future_promise_type<void> : public promise_base
{
public:
Expand Down Expand Up @@ -589,7 +587,7 @@ private:
usize m_frame_size = 0;
};

template<typename T>
export template<typename T>
class future
{
public:
Expand All @@ -599,7 +597,7 @@ public:

constexpr void resume() const
{
auto active = handle().promise().context().active_handle();
auto active = handle().promise().get_context().active_handle();
active.resume();
}

Expand Down
3 changes: 3 additions & 0 deletions test_package/.clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CompileFlags:
CompilationDatabase: .
BuiltinHeaders: QueryDriver
44 changes: 44 additions & 0 deletions test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2024 - 2025 Khalil Estell and the libhal contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 4.0)

# Generate compile commands for anyone using our libraries.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_COLOR_DIAGNOSTICS ON)

project(test_package LANGUAGES CXX)

# Require Ninja or Visual Studio for modules
if(NOT CMAKE_GENERATOR MATCHES "Ninja|Visual Studio")
message(FATAL_ERROR "C++20 modules require Ninja or Visual Studio generator")
endif()

find_package(async_context REQUIRED CONFIG)

add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PUBLIC
FILE_SET CXX_MODULES
TYPE CXX_MODULES
PRIVATE main.cpp
)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23)
target_link_libraries(${PROJECT_NAME} PRIVATE async_context)

# Always run this custom target by making it depend on ALL
add_custom_target(copy_compile_commands ALL
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_BINARY_DIR}/compile_commands.json
${CMAKE_SOURCE_DIR}/compile_commands.json
DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json)
52 changes: 52 additions & 0 deletions test_package/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/python
#
# Copyright 2024 - 2025 Khalil Estell and the libhal contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from conan import ConanFile
from conan.tools.build import cross_building
from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain, CMakeDeps
from pathlib import Path


class TestPackageConan(ConanFile):
settings = "os", "arch", "compiler", "build_type"
generators = "VirtualRunEnv"

def build_requirements(self):
self.tool_requires("cmake/[^4.0.0]")
self.tool_requires("ninja/[^1.3.0]")

def requirements(self):
self.requires(self.tested_reference_str)

def layout(self):
cmake_layout(self)

def generate(self):
tc = CMakeToolchain(self)
tc.generate()

deps = CMakeDeps(self)
deps.generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def test(self):
if not cross_building(self):
bin_path = Path(self.cpp.build.bindirs[0]) / "test_package"
self.run(bin_path.absolute(), env="conanrun")
83 changes: 83 additions & 0 deletions test_package/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2024 - 2025 Khalil Estell and the libhal contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cassert>

#include <array>
#include <chrono>
#include <coroutine>
#include <memory_resource>
#include <print>
#include <span>
#include <variant>

import async_context;

struct my_scheduler : public async::scheduler
{
int sleep_count = 0;

private:
void do_schedule(
[[maybe_unused]] async::context& p_context,
[[maybe_unused]] async::blocked_by p_block_state,
[[maybe_unused]] std::variant<std::chrono::nanoseconds, async::context*>
p_block_info) override
{
if (std::holds_alternative<std::chrono::nanoseconds>(p_block_info)) {
sleep_count++;
}
}
};

async::future<void> coro_double_delay(async::context&)
{
using namespace std::chrono_literals;
std::println("Delay for 500ms");
co_await 500ms;
std::println("Delay for another 500ms");
co_await 500ms;
std::println("Returning!");
co_return;
}

int main()
{
auto scheduler =
mem::make_strong_ptr<my_scheduler>(std::pmr::new_delete_resource());
auto buffer = mem::make_strong_ptr<std::array<async::u8, 1024>>(
std::pmr::new_delete_resource());
auto buffer_span = mem::make_strong_ptr<std::span<async::u8>>(
std::pmr::new_delete_resource(), *buffer);
async::context my_context(scheduler, buffer_span);

auto future_delay = coro_double_delay(my_context);

assert(not future_delay.done());

future_delay.resume();

assert(scheduler->sleep_count == 1);

future_delay.resume();

assert(scheduler->sleep_count == 2);
assert(not future_delay.done());

future_delay.resume();

assert(future_delay.done());

return 0;
}
14 changes: 8 additions & 6 deletions tests/async.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ boost::ut::suite<"async::context"> async_context_suite = []() {
int sleep_count = 0;

private:
void do_schedule([[maybe_unused]] context& p_context,
[[maybe_unused]] blocked_by p_block_state,
[[maybe_unused]] std::variant<sleep_duration, context*>
p_block_info) override
void do_schedule(
[[maybe_unused]] context& p_context,
[[maybe_unused]] blocked_by p_block_state,
[[maybe_unused]] std::variant<std::chrono::nanoseconds, context*>
p_block_info) override
{
std::println("Scheduler called!", sleep_count);
if (std::holds_alternative<sleep_duration>(p_block_info)) {
std::println("sleep for: {}", std::get<sleep_duration>(p_block_info));
if (std::holds_alternative<std::chrono::nanoseconds>(p_block_info)) {
std::println("sleep for: {}",
std::get<std::chrono::nanoseconds>(p_block_info));
sleep_count++;
std::println("Sleep count = {}!", sleep_count);
}
Expand Down
Loading