/src/Utilities/contextswitch.h

https://github.com/zeroality2/pokemon-online · C Header · 181 lines · 63 code · 25 blank · 93 comment · 0 complexity · 1f507ec139ea613361a60b34aa3752d9 MD5 · raw file

  1. #ifndef CONTEXTSWITCH_H
  2. #define CONTEXTSWITCH_H
  3. #include <QtCore>
  4. #include "coro.h"
  5. /*
  6. This file is for co-routines. Check on wikipedia what that is.
  7. Coroutines are like threads, but without concurrent access.
  8. A function / object that is supposed to run in a new coroutine (as with threads)
  9. will be given a reference to this ContextSwitcher below. Let's call that reference
  10. self. The function will be executed normally, until the function calls self.yield().
  11. When it does so, the context switcher will swap back to the main context. Then
  12. whatever manager/scheduler will decide to continue the function, and so the execution
  13. of the function will continue until it calls yield() again. Basically it's the same as a
  14. thread which would call sem.acquire(), and stay blocked until the main thread calls sem.release().
  15. The reason for having this on PO is to prevent having a high number of threads, because the battles
  16. used to be each in a new thread and then on the Beta Server where over 80 battles would be running
  17. it would cause serious problems.
  18. This was developped specifically for PO so it's not as generic as you would like. And if not used with
  19. threads it could fail badly, if you want info / examples drop on #po on irc.freenode.net, or better, on
  20. our forums.
  21. If you would like a concrete example of coroutines to understand how it works, create a main.cpp with that:
  22. #include <iostream>
  23. extern "C" {
  24. #include "coro.h"
  25. }
  26. using namespace std;
  27. coro_context a, b, init;
  28. void foo(void *)
  29. {
  30. for (int i = 0; i < 10; i++) {
  31. cout << i * 2 << endl;
  32. coro_transfer(&a, &b);
  33. }
  34. // Returning to main
  35. coro_transfer(&a, &init);
  36. }
  37. void foo2(void *)
  38. {
  39. for (int i = 0; i < 10; i++) {
  40. cout << (i * 2)+1 << endl;
  41. coro_transfer(&b, &a);
  42. }
  43. //Never reached but safety mesure
  44. coro_transfer(&b, &init);
  45. }
  46. int main()
  47. {
  48. coro_create(&init, NULL, NULL, NULL, 0);
  49. void *stack1 = malloc(256*256);
  50. void *stack2 = malloc(256*256);
  51. coro_create(&a, foo, NULL, stack1, 256*256);
  52. coro_create(&b, foo2, NULL, stack2, 256*256);
  53. coro_transfer(&init, &a);
  54. free(stack1);
  55. free(stack2);
  56. cout << "Hello world!" << endl;
  57. return 0;
  58. }
  59. And figure out why it displays all numbers in order =p
  60. (Dont forget to include coro.c and coro.h in your project)
  61. */
  62. class ContextCallee;
  63. class ContextQuitEx {
  64. };
  65. class ContextSwitcher : public QThread
  66. {
  67. friend class ContextCallee;
  68. public:
  69. enum Scheduling {
  70. Start = 0,
  71. Continue = 1,
  72. Cease = 2
  73. };
  74. typedef QPair<ContextCallee *, Scheduling> pair;
  75. typedef QPair<ContextSwitcher *, ContextCallee *> startpair;
  76. ContextSwitcher();
  77. ~ContextSwitcher();
  78. /* Starts the main loop. */
  79. void run();
  80. /* Thread safe. Ends the run() by throwing an exception, that is caught. It will be executed in the ContextSwitcher thread
  81. so it might not execute directly, but will do as soon as the current ContextCallee yields. */
  82. void terminate(ContextCallee *c);
  83. private:
  84. /* Creating contexts is not even reentrant, but with a mutex
  85. it's fine */
  86. static QMutex guardian;
  87. QMutex ownGuardian;
  88. QSemaphore streamController;
  89. coro_context main_context;
  90. QSet<ContextCallee *> contexts;
  91. ContextCallee *current_context;
  92. ContextCallee *context_to_delete;
  93. QList<pair> scheduled;
  94. void create_context(coro_context *c, coro_func function=NULL, void *param=NULL, void *stack=NULL, long stacksize=0);
  95. void switch_context(ContextCallee *new_context);
  96. protected:
  97. /* Adds the callee and runs it */
  98. void runNewCallee(ContextCallee *callee);
  99. /* Takes a QPair<ContextSwitcher*, ContextCalle*> * as a parameter. Freeing the arg if dynamicly allocated
  100. is the responsability of the caller */
  101. static void runNewCalleeS(void *);
  102. void schedule(ContextCallee *c);
  103. void yield();
  104. };
  105. class ContextCallee : public QObject
  106. {
  107. friend class ContextSwitcher;
  108. public:
  109. ContextCallee(long stacksize = 100*1000);
  110. ~ContextCallee();
  111. void start(ContextSwitcher &ctx);
  112. virtual void run() = 0;
  113. /* If called in a different thread, will wait till the context stuff ended. If in the same
  114. thread, you're badly screwed. */
  115. void wait();
  116. /* Asks to be rescheduled asap. Thread safe. */
  117. void schedule();
  118. bool finished();
  119. /* Used to be terminated. Thread safe */
  120. void terminate();
  121. protected:
  122. /* Gives the hand back to the Context Switcher */
  123. void yield();
  124. /* Terminate the context, but doesn't delete the class. In short it just exits of the run() function and cleanly at that.
  125. Though in practice you do not need to call it if you don't need it. And only callable from the main context, or you'll have
  126. big problems */
  127. void exit();
  128. private:
  129. ContextSwitcher *ctx;
  130. long stacksize;
  131. void *stack;
  132. bool needsToExit;
  133. volatile bool _finished;
  134. coro_context context;
  135. };
  136. #endif // CONTEXTSWITCH_H