@@ -26,9 +26,13 @@ SOFTWARE.
2626
2727#pragma once
2828
29+ #include < EASTL/functional.h>
30+ #include < EASTL/utility.h>
31+
2932#include < coroutine>
3033#include < type_traits>
3134
35+ #include " common/psxlibc/ucontext.h"
3236#include " common/syscalls/syscalls.h"
3337
3438namespace psyqo {
@@ -208,4 +212,97 @@ struct Coroutine {
208212 }
209213};
210214
215+ class StackfulBase {
216+ protected:
217+ void initializeInternal (eastl::function<void ()>&& func, void* ss_sp, unsigned ss_size);
218+ void resume ();
219+ void yield ();
220+ [[nodiscard]] bool isAlive () const { return m_isAlive; }
221+
222+ StackfulBase () = default ;
223+ StackfulBase (const StackfulBase&) = delete ;
224+ StackfulBase& operator =(const StackfulBase&) = delete ;
225+
226+ private:
227+ static void trampoline (void * arg) {
228+ StackfulBase* self = static_cast <StackfulBase*>(arg);
229+ self->trampoline ();
230+ }
231+ void trampoline ();
232+ ucontext_t m_coroutine;
233+ ucontext_t m_return;
234+ eastl::function<void ()> m_func;
235+ bool m_isAlive = false ;
236+ };
237+
238+ /* *
239+ * @brief Stackful coroutine class.
240+ *
241+ * @details This class provides a simple stackful coroutine implementation.
242+ * It allows you to create coroutines that can yield and resume execution.
243+ * While the Coroutine class above is a C++20 coroutine, it requires
244+ * that all of the code being run are coroutines or awaitables all the way down.
245+ * This class is a more traditional coroutine implementation that uses
246+ * a separate stack for each coroutine, allowing it to yield and resume
247+ * execution without requiring the entire call stack to be coroutine-aware.
248+ * It is suitable for use in scenarios where you need to yield execution
249+ * from legacy code without converting it to C++20 coroutines.
250+ */
251+ template <unsigned StackSize = 0x10000 >
252+ class Stackful : public StackfulBase {
253+ public:
254+ static constexpr unsigned c_stackSize = (StackSize + 7 ) & ~7 ;
255+
256+ Stackful () = default ;
257+ Stackful (const Stackful&) = delete ;
258+ Stackful& operator =(const Stackful&) = delete ;
259+
260+ /* *
261+ * @brief Initialize the coroutine with a function and an argument.
262+ *
263+ * @param func Function to be executed by the coroutine.
264+ * @param arg Argument to be passed to the function.
265+ */
266+ void initialize (eastl::function<void ()>&& func) {
267+ initializeInternal (eastl::move (func), m_stack.data , c_stackSize);
268+ }
269+
270+ /* *
271+ * @brief Resume the coroutine.
272+ *
273+ * @details This will switch to the coroutine's context and execute it.
274+ * If the coroutine is not alive, this function does nothing. This
275+ * function should be called after the coroutine has been initialized,
276+ * and it will return to the point where the coroutine was last yielded.
277+ * It can only be called from the "main thread".
278+ */
279+ void resume () { StackfulBase::resume (); }
280+
281+ /* *
282+ * @brief Yield the coroutine.
283+ *
284+ * @details This will switch back to the main thread and save the
285+ * coroutine's context. The coroutine can be resumed later using
286+ * `resume()`. It can only be called from within the coroutine
287+ * to yield execution.
288+ */
289+ void yield () { StackfulBase::yield (); }
290+
291+ /* *
292+ * @brief Check if the coroutine is currently alive.
293+ * @details A coroutine is considered alive if it has been initialized
294+ * and has not yet completed its execution. It becomes not alive
295+ * when it returns from its function.
296+ *
297+ * @return true if the coroutine is alive, false otherwise.
298+ */
299+ [[nodiscard]] bool isAlive () const { return StackfulBase::isAlive (); }
300+
301+ private:
302+ struct alignas (8 ) Stack {
303+ uint8_t data[c_stackSize];
304+ };
305+ Stack m_stack;
306+ };
307+
211308} // namespace psyqo
0 commit comments