summaryrefslogtreecommitdiffstats
path: root/src/fiber.cpp
blob: 7208ceddef8ed3724f9b2efc16e650421c90f520 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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);
}