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
13 changes: 7 additions & 6 deletions include/tinycoro/Promise.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ namespace tinycoro {
{
auto& promise = hdl.promise();

if (promise.parent)
if (auto* parent = promise.Parent())
{
using promise_t = std::remove_pointer_t<decltype(promise.parent)>;
// reset the parent child,
// because we are done.
promise.parent->child = nullptr;
using promise_t = std::remove_pointer_t<decltype(promise.conti)>;
auto sharedState = promise.SharedState();

// pop the first element from the stack.
sharedState->conti = parent;

// We can strait jump and resume
// the parent coroutine.
return std::coroutine_handle<promise_t>::from_promise(*promise.parent);
return std::coroutine_handle<promise_t>::from_promise(*parent);
}

return std::noop_coroutine();
Expand Down
44 changes: 22 additions & 22 deletions include/tinycoro/PromiseBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ namespace tinycoro { namespace detail {
{
auto stopTokenUser = SharedState() ? SharedState()->IsStopTokenUser() : false;

if (parent == nullptr && stopTokenUser == false)
if (Parent() == nullptr && stopTokenUser == false)
{
// The parent coroutine is nullptr,
// that means this is a root
Expand All @@ -67,11 +67,11 @@ namespace tinycoro { namespace detail {
// Disallow copy and move
PromiseBase(PromiseBase&&) = delete;

// These are navigation pointers used to
// resume and chain together coroutines,
// enabling continuous execution.
PromiseBase_t* parent{nullptr};
PromiseBase_t* child{nullptr};
// Link to the previous promise (parent) in the continuation chain.
// When a task awaits another task, the child promise is pushed
// onto "SharedState::conti" head and this field points to the promise
// that was previously at the top of that stack.
PromiseBase_t* conti{nullptr};

// At the beginning we not initialize
// the stop source here, the initialization
Expand Down Expand Up @@ -106,8 +106,8 @@ namespace tinycoro { namespace detail {
// - the current awaitable pointer for CoWait constructs, or
// - any data required for resumption in a TaskGroup.
void SetCustomData(void* data) noexcept
{
assert(parent == nullptr); // need to be a root corouitne
{
assert(Parent() == nullptr); // need to be a root corouitne
assert(_customData == nullptr); // must not be set

_customData = data;
Expand All @@ -116,7 +116,7 @@ namespace tinycoro { namespace detail {
// Get the stored custom data and clears it.
[[nodiscard]] constexpr auto CustomData() const noexcept
{
assert(parent == nullptr); // need to be a root corouitne
assert(Parent() == nullptr); // need to be a root corouitne

return _customData;
}
Expand All @@ -127,32 +127,32 @@ namespace tinycoro { namespace detail {

constexpr void unhandled_exception() const { std::rethrow_exception(std::current_exception()); }

constexpr auto SharedState() noexcept
{
return detail::PtrVisit(_sharedState);
}
constexpr auto SharedState() noexcept { return detail::PtrVisit(_sharedState); }

constexpr auto SharedState() const noexcept
{
return detail::PtrVisit(_sharedState);
}
constexpr auto SharedState() const noexcept { return detail::PtrVisit(_sharedState); }

constexpr void AssignSharedState(auto sharedStatePtr) noexcept
{
assert(sharedStatePtr);
assert(parent != nullptr);
assert(Parent() != nullptr);

_sharedState = sharedStatePtr;
}

protected:
constexpr PromiseBase_t* Parent() const noexcept { return conti; }

protected:
constexpr void CreateSharedState(bool initialCancellable = tinycoro::default_initial_cancellable_policy::value) noexcept
{
// make sure this is called only once
assert(SharedState() == nullptr);
assert(parent == nullptr);
assert(Parent() == nullptr);

// create the shared state
auto& sharedState = _sharedState.emplace<detail::SharedState>(initialCancellable);

_sharedState.emplace<detail::SharedState>(initialCancellable);
// set continuation as this
sharedState.conti = this;
}

private:
Expand All @@ -172,4 +172,4 @@ namespace tinycoro { namespace detail {

}} // namespace tinycoro::detail

#endif // TINY_CORO_PROMISE_BASE_HPP
#endif // TINY_CORO_PROMISE_BASE_HPP
2 changes: 1 addition & 1 deletion include/tinycoro/PromiseSchedulable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace tinycoro
#endif

template <std::unsigned_integral auto BUFFER_SIZE, concepts::IsAwaiter FinalAwaiterT, typename StopSourceT>
struct SchedulablePromise : PromiseBase<FinalAwaiterT, StopSourceT>, detail::DoubleLinkable<SchedulablePromise<BUFFER_SIZE, FinalAwaiterT, StopSourceT>>
struct SchedulablePromise : PromiseBase<FinalAwaiterT, StopSourceT>, detail::SingleLinkable<SchedulablePromise<BUFFER_SIZE, FinalAwaiterT, StopSourceT>>
{
static_assert(BUFFER_SIZE >= PROMISE_BASE_BUFFER_SIZE, "SchedulablePromise: Buffer size is too small to hold the promise object.");

Expand Down
7 changes: 6 additions & 1 deletion include/tinycoro/SharedState.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ namespace tinycoro {
// clear the first bits which are responsible for the pause state in scheduler.
return _state.fetch_and(~detail::UTypeCast(detail::EPauseState::IDLE), order);
};

// Type-erased head of the continuation stack.
// The stack contains promise objects linked through
// PromiseBase::conti, with the most recently awaited task on top.
void* conti{nullptr};

private:
// atomic flag to store shared state
Expand Down Expand Up @@ -150,4 +155,4 @@ namespace tinycoro {

} // namespace tinycoro

#endif // TINY_CORO_SHARED_STATE_HPP
#endif // TINY_CORO_SHARED_STATE_HPP
14 changes: 11 additions & 3 deletions include/tinycoro/TaskAwaiter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ namespace tinycoro {
auto hdl = coroTask->_hdl;
auto& promise = hdl.promise();

parentPromise.child = std::addressof(promise);
promise.parent = std::addressof(parentPromise);
using promiseBase_t = decltype(promise.conti);

auto* sharedState = parentPromise.SharedState();
auto* conti = static_cast<promiseBase_t>(sharedState->conti);

assert(conti);

promise.conti = conti;
sharedState->conti = &promise;

promise.stopSource = parentPromise.StopSource();
promise.AssignSharedState(parentPromise.SharedState());
promise.AssignSharedState(sharedState);

return hdl;
}
Expand Down
36 changes: 9 additions & 27 deletions include/tinycoro/TaskResumer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,29 @@ namespace tinycoro { namespace detail {

struct TaskResumer
{
// Find the last continuation
// and set up the loop at the end
// if necessary.
template <typename PromiseBaseT>
[[nodiscard]] static inline auto FindContinuation(PromiseBaseT* promisePtr) noexcept
{
assert(promisePtr);

// Iterate until we found the last corouinte
// in the chain, which we need to resume.
while (promisePtr->child != nullptr)
{
// moving forward...
promisePtr = promisePtr->child;
}

return promisePtr;
}

template <typename PromiseT>
static inline void Resume(PromiseT& promise)
{
auto sharedStatePtr = promise.SharedState();
const auto& stopSource = promise.stopSource;
auto* sharedState = promise.SharedState();
const auto& stopSource = promise.stopSource;

assert(sharedState);
assert(sharedState->conti);

// reset the pause state by every resume.
sharedStatePtr->ClearPauseStateBits();
sharedState->ClearPauseStateBits();

if (sharedStatePtr->IsCancellable() && stopSource.stop_requested())
if (sharedState->IsCancellable() && stopSource.stop_requested())
{
return; // need to cancel the corouitne
}

// Resets all the flags.
sharedStatePtr->ClearFlags();
sharedState->ClearFlags();

// check for continuation type
using promise_base_t = PromiseT::PromiseBase_t;

// find the continuation
auto promiseToResume = FindContinuation<promise_base_t>(std::addressof(promise));
auto* promiseToResume = static_cast<promise_base_t*>(sharedState->conti);

// Resume the coroutine.
//
Expand Down
25 changes: 15 additions & 10 deletions test/src/TaskAwaiter_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ struct SharedStateMock
{
}

void* conti;

size_t val;
};

Expand Down Expand Up @@ -83,8 +85,7 @@ struct HandleMock<void>
template<typename ValueT>
struct PromiseMock
{
PromiseMock<ValueT>* child;
PromiseMock<ValueT>* parent;
PromiseMock<ValueT>* conti;
StopSourceMock stopSource;
SharedStateMock sharedState;

Expand All @@ -105,8 +106,7 @@ struct PromiseMock
template<>
struct PromiseMock<void>
{
PromiseMock<void>* child;
PromiseMock<void>* parent;
PromiseMock<void>* conti;
StopSourceMock stopSource;
SharedStateMock sharedState;

Expand All @@ -125,6 +125,11 @@ struct CoroTaskMock : public AwaiterT<ValueT, CoroTaskMock<ValueT, AwaiterT>>
{
using handle_type = tinycoro::test::CoroutineHandleMock<PromiseMock<ValueT>>;

void InitConti()
{
_hdl.promise().sharedState.conti = &_hdl.promise();
}

handle_type _hdl;
};

Expand Down Expand Up @@ -165,23 +170,23 @@ TEST(TaskAwaiterTest, TaskAwaiterTest_await_suspend_int)
task._hdl.promise()._value = 42;

CoroTaskMock<int32_t, tinycoro::AwaiterValue> parent;
parent.InitConti();

std::ignore = task.await_suspend(parent._hdl);

EXPECT_EQ(parent._hdl.promise().child->value(), 42);

EXPECT_EQ(task._hdl.promise().parent->sharedState.val, parent._hdl.promise().sharedState.val);
EXPECT_EQ(task._hdl.promise().parent->stopSource.val, parent._hdl.promise().stopSource.val);
EXPECT_EQ(task._hdl.promise().conti->sharedState.val, parent._hdl.promise().sharedState.val);
EXPECT_EQ(task._hdl.promise().conti->stopSource.val, parent._hdl.promise().stopSource.val);
}

TEST(TaskAwaiterTest, TaskAwaiterTest_await_suspend_void)
{
CoroTaskMock<void, tinycoro::AwaiterValue> task;

CoroTaskMock<void, tinycoro::AwaiterValue> parent;
parent.InitConti();

std::ignore = task.await_suspend(parent._hdl);

EXPECT_EQ(task._hdl.promise().parent->sharedState.val, parent._hdl.promise().sharedState.val);
EXPECT_EQ(task._hdl.promise().parent->stopSource.val, parent._hdl.promise().stopSource.val);
EXPECT_EQ(task._hdl.promise().conti->sharedState.val, parent._hdl.promise().sharedState.val);
EXPECT_EQ(task._hdl.promise().conti->stopSource.val, parent._hdl.promise().stopSource.val);
}
33 changes: 0 additions & 33 deletions test/src/TaskResumer_test.cpp

This file was deleted.

30 changes: 30 additions & 0 deletions test/src/YieldValue_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ TEST_P(YieldValueTest, YieldValueTest_generator)
// See: https://stackoverflow.com/questions/67860049/why-cant-co-await-return-a-string
#ifndef TINY_CORO_GCC_11

tinycoro::Task<int32_t> NestedYieldCoroutine()
{
co_yield 41;
co_return 42;
}

tinycoro::Task<int32_t> IntYieldCoroutine()
{
auto nested = NestedYieldCoroutine();
co_yield co_await nested;
co_return co_await nested;
}

tinycoro::Task<std::variant<int32_t, bool>> YieldCoroutine()
{
co_yield 41;
Expand All @@ -73,6 +86,23 @@ TEST(YieldValueTest, YieldValueTest_variant_yield)
tinycoro::AllOf(scheduler, runner());
}

TEST(YieldValueTest, NestedYieldValueTest_int_yield)
{
tinycoro::Scheduler scheduler;

auto runner = []() -> tinycoro::Task<void> {
auto task = IntYieldCoroutine();

auto val = co_await task;
EXPECT_EQ(val, 41);

val = co_await task;
EXPECT_EQ(val, 42);
};

tinycoro::AllOf(scheduler, runner());
}

TEST(YieldValueTest, YieldValueTest_variant_yield_runinline)
{
auto runner = []() -> tinycoro::Task<void> {
Expand Down
Loading