summaryrefslogtreecommitdiffstats
path: root/src/fiber.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fiber.cpp')
-rw-r--r--src/fiber.cpp120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/fiber.cpp b/src/fiber.cpp
new file mode 100644
index 0000000..7208ced
--- /dev/null
+++ b/src/fiber.cpp
@@ -0,0 +1,120 @@
+#include <stdlib.h>
+#include <QtCore/QtGlobal>
+
+#include "fiber.h"
+
+/*!
+ \class Fiber
+ \brief The Fiber class provides cooperatively scheduled stacks of execution.
+
+ Fibers, also known as coroutines, allow managing multiple stacks in the same
+ thread.
+
+ To create a fiber, subclass Fiber and override the run() method. To run it,
+ call cont(). This will execute the code in run() until it calls Fiber::yield().
+ At that point, the call to cont() returns. Subsequent calls to cont() will
+ continue execution of the fiber just after the yield().
+
+ Example:
+ class MyFiber : public Fiber
+ {
+ virtual void run()
+ {
+ qDebug() << "1";
+ Fiber::yield();
+ qDebug() << "2";
+ }
+ }
+
+ MyFiber fib;
+ qDebug() << "0.5";
+ fib.cont(); // prints 1
+ qDebug() << "1.5";
+ fib.cont(); // prints 2
+*/
+
+#ifdef Q_OS_MAC
+extern "C" void switchStackInternal(void* to, void** from);
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer);
+void switchStack(void* to, void** from) { switchStackInternal(to, from); }
+#else
+extern "C" void _switchStackInternal(void* to, void** from);
+void initializeStack(void *data, int size, void (*entry)(), void **stackPointer);
+void switchStack(void* to, void** from) { _switchStackInternal(to, from); }
+#endif
+
+Fiber *Fiber::_currentFiber = 0;
+
+Fiber::Fiber(int stackSize)
+ : _stackData(0)
+ , _stackPointer(0)
+ , _previousFiber(0)
+ , _status(NotStarted)
+{
+ // establish starting fiber context if necessary
+ currentFiber();
+
+ _stackData = malloc(stackSize);
+ initializeStack(_stackData, stackSize, &entryPoint, &_stackPointer);
+}
+
+Fiber::Fiber(bool)
+ : _stackData(0)
+ , _stackPointer(0)
+ , _previousFiber(0)
+ , _status(Running)
+{
+}
+
+Fiber::~Fiber()
+{
+ if (_stackData)
+ free(_stackData);
+}
+
+Fiber *Fiber::currentFiber()
+{
+ // establish a context for the starting fiber
+ if (!_currentFiber)
+ _currentFiber = new Fiber(true);
+
+ return _currentFiber;
+}
+
+void Fiber::entryPoint()
+{
+ _currentFiber->run();
+ yieldHelper(Terminated);
+ Q_ASSERT(0); // unreachable
+}
+
+// returns whether it can be continued again
+bool Fiber::cont()
+{
+ Q_ASSERT(_status == NotStarted || _status == Stopped);
+ Q_ASSERT(!_previousFiber);
+
+ _previousFiber = _currentFiber;
+ _currentFiber = this;
+ _status = Running;
+ switchStack(_stackPointer, &_previousFiber->_stackPointer);
+ return _status != Terminated;
+}
+
+void Fiber::yield()
+{
+ yieldHelper(Stopped);
+}
+
+void Fiber::yieldHelper(Status stopStatus)
+{
+ Fiber *stoppingFiber = _currentFiber;
+ Q_ASSERT(stoppingFiber);
+ Q_ASSERT(stoppingFiber->_previousFiber);
+ Q_ASSERT(stoppingFiber->_status == Running);
+
+ _currentFiber = stoppingFiber->_previousFiber;
+ stoppingFiber->_previousFiber = 0;
+ stoppingFiber->_status = stopStatus;
+ switchStack(_currentFiber->_stackPointer, &stoppingFiber->_stackPointer);
+}