| 1 | /* $Id$ $URL$ */ |
|---|
| 2 | #ifndef SEMWAITER_DOT_AITCH |
|---|
| 3 | #define SEMWAITER_DOT_AITCH |
|---|
| 4 | |
|---|
| 5 | /**\file semwaiter.h |
|---|
| 6 | * Declaration of SemWaiter |
|---|
| 7 | * \author Trent Apted <tapted@it.usyd.edu.au> |
|---|
| 8 | * $Rev$ |
|---|
| 9 | * $Date$ |
|---|
| 10 | */ |
|---|
| 11 | |
|---|
| 12 | #include <SDL_thread.h> |
|---|
| 13 | |
|---|
| 14 | /** A simple C++ Sempahore wrapper */ |
|---|
| 15 | class SemWaiter { |
|---|
| 16 | /** Assignment is disabled */ |
|---|
| 17 | SemWaiter& operator=(const SemWaiter&); |
|---|
| 18 | protected: |
|---|
| 19 | SDL_sem *sem; ///< The SDL Semaphore we are wrapping |
|---|
| 20 | volatile bool run; ///< Whether we keep running |
|---|
| 21 | Uint32 initial_resources; ///< How many resources are available for copies |
|---|
| 22 | public: |
|---|
| 23 | /** Constructor creates the semaphore and inits data members */ |
|---|
| 24 | SemWaiter (const SemWaiter& rhs) |
|---|
| 25 | : |
|---|
| 26 | sem(SDL_CreateSemaphore(rhs.initial_resources)), run(true), initial_resources(rhs.initial_resources) {} |
|---|
| 27 | |
|---|
| 28 | /** Create a Semaphore, initially set to have \a "resources" */ |
|---|
| 29 | explicit SemWaiter(Uint32 resources = 0) |
|---|
| 30 | : |
|---|
| 31 | sem(SDL_CreateSemaphore(resources)), run(true), initial_resources(resources) {} |
|---|
| 32 | |
|---|
| 33 | /** Destroy the Semaphore */ |
|---|
| 34 | ~SemWaiter() {SDL_DestroySemaphore(sem); sem = 0; run = false;} |
|---|
| 35 | |
|---|
| 36 | /** Post to the Semaphore -- increment it -- notify next waiter that a resource is available */ |
|---|
| 37 | void post() {SDL_SemPost(sem);} |
|---|
| 38 | |
|---|
| 39 | /** Stop the SemWaiter -- don't allow further wait()s and post to (at most) 1 waiter waiting */ |
|---|
| 40 | void stop() {run = false; post();} |
|---|
| 41 | |
|---|
| 42 | /** Don't allow further wait()s to block, but don't post() */ |
|---|
| 43 | void cancel() {run = false;} |
|---|
| 44 | |
|---|
| 45 | /** Wrest control from another SemWaiter. |
|---|
| 46 | * Normally called from an object's ~Destructor() like |
|---|
| 47 | *\code |
|---|
| 48 | * destroy.wrest(thingo_ready, &thingo_processor); |
|---|
| 49 | *\endcode |
|---|
| 50 | * Where the last act of the thingo_processor thread will be to call |
|---|
| 51 | * stop(). |
|---|
| 52 | * \see DestroyWaiter |
|---|
| 53 | */ |
|---|
| 54 | int wrest(SemWaiter &alt, SDL_Thread **thr, Uint32 timeout = 0) { |
|---|
| 55 | alt.stop(); |
|---|
| 56 | return threadwait(thr, timeout); |
|---|
| 57 | } |
|---|
| 58 | /** Wait for a thread to terminate, if non-null */ |
|---|
| 59 | int threadwait(SDL_Thread **thr, Uint32 timeout = 0) { |
|---|
| 60 | int r = -1; |
|---|
| 61 | /* if \a thr is NULL, we assume the thread was never started, |
|---|
| 62 | * and so there will be no response to post to something waiting |
|---|
| 63 | * in that thread, hence no way for the next wait() to return |
|---|
| 64 | */ |
|---|
| 65 | if (thr && *thr) { |
|---|
| 66 | if (timeout) { |
|---|
| 67 | if (!timedwait(true, timeout)) { |
|---|
| 68 | SDL_KillThread(*thr); |
|---|
| 69 | return -1; |
|---|
| 70 | } |
|---|
| 71 | } else { |
|---|
| 72 | wait(); |
|---|
| 73 | } |
|---|
| 74 | SDL_WaitThread(*thr, &r); |
|---|
| 75 | *thr = 0; |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | return r; |
|---|
| 79 | } |
|---|
| 80 | /** |
|---|
| 81 | * Wait for this semaphore -- decrease available resources. |
|---|
| 82 | * |
|---|
| 83 | * \param spin If true we keep trying SemWait until it returns success (when debugging |
|---|
| 84 | * for example, it is common for the system call to get interrupted here) |
|---|
| 85 | */ |
|---|
| 86 | bool wait(bool spin = true) { |
|---|
| 87 | bool b = false; |
|---|
| 88 | while (run && (b = (SDL_SemWait(sem) == -1)) && spin) |
|---|
| 89 | ; //spin ==> !b || !run |
|---|
| 90 | return run && !b; |
|---|
| 91 | } |
|---|
| 92 | bool timedwait(bool spin = true, Uint32 timeout = 0) { |
|---|
| 93 | bool b = false; |
|---|
| 94 | while (run && (b = (SDL_SemWaitTimeout(sem, timeout) == -1)) && spin) |
|---|
| 95 | ; //spin ==> !b || !run |
|---|
| 96 | if (b == SDL_MUTEX_TIMEDOUT) { |
|---|
| 97 | /* timed out : b != 0 -> return false*/ |
|---|
| 98 | } |
|---|
| 99 | return run && !b; |
|---|
| 100 | } |
|---|
| 101 | /** Overloaded Wait calls threadwait() */ |
|---|
| 102 | int wait(SDL_Thread **thr, Uint32 timeout = 0) {return threadwait(thr, timeout);} |
|---|
| 103 | |
|---|
| 104 | /** HAS SIDEFFECTS: returns wait() if we are running or false */ |
|---|
| 105 | operator bool() {return run ? wait() : false;} |
|---|
| 106 | |
|---|
| 107 | /** Return true if we are running */ |
|---|
| 108 | bool running() const {return run;} |
|---|
| 109 | |
|---|
| 110 | //virtual ~SemWaiter() {} |
|---|
| 111 | }; |
|---|
| 112 | |
|---|
| 113 | /** A SemWaiter for handling object destruction */ |
|---|
| 114 | class DestroyWaiter : private SemWaiter { |
|---|
| 115 | enum {DESTROYED_SEMVALUE = 2}; |
|---|
| 116 | public: |
|---|
| 117 | /** Creates a SemWaiter with a single resource */ |
|---|
| 118 | DestroyWaiter() : SemWaiter(DESTROYED_SEMVALUE) { |
|---|
| 119 | while (SDL_SemValue(sem) != 1) |
|---|
| 120 | SDL_SemWait(sem); |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | /** |
|---|
| 124 | * Indicate that we want to destroy X |
|---|
| 125 | * |
|---|
| 126 | * \param alternate A SemWaiter that \a thr might be wait()ing on |
|---|
| 127 | * \param thr An optional thread; wait for this to complete after |
|---|
| 128 | * stopping \a alternate |
|---|
| 129 | * \param timeout Timeout in milliseconds if we need to wrest control from the thread |
|---|
| 130 | */ |
|---|
| 131 | int wants_to_destroy(SemWaiter &alternate, SDL_Thread **thr, Uint32 timeout = 0) { |
|---|
| 132 | if (!running()) |
|---|
| 133 | return true; |
|---|
| 134 | return wrest(alternate, thr, timeout); |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | /** |
|---|
| 138 | * Indicate that we want to destroy X |
|---|
| 139 | * |
|---|
| 140 | * \param thr An optional thread that we will wait for completion |
|---|
| 141 | * \param timeout timeout for waits, in milliseconds |
|---|
| 142 | */ |
|---|
| 143 | int wants_to_destroy(SDL_Thread **thr = 0, Uint32 timeout = 0) { |
|---|
| 144 | int r = -1; |
|---|
| 145 | |
|---|
| 146 | if (timeout) { |
|---|
| 147 | if (!timedwait(true, timeout) && thr && *thr) { |
|---|
| 148 | /* probably timed out */ |
|---|
| 149 | SDL_KillThread(*thr); |
|---|
| 150 | *thr = 0; |
|---|
| 151 | } |
|---|
| 152 | cancel(); |
|---|
| 153 | } else { |
|---|
| 154 | cancel(); |
|---|
| 155 | while (SDL_SemWait(sem) != 0) |
|---|
| 156 | ; |
|---|
| 157 | SDL_SemPost(sem); // never let it stay at 0 |
|---|
| 158 | } |
|---|
| 159 | if (thr && *thr) { |
|---|
| 160 | SDL_WaitThread(*thr, &r); |
|---|
| 161 | *thr = 0; |
|---|
| 162 | } |
|---|
| 163 | /* there is a race condition here, but we don't really care */ |
|---|
| 164 | while (SDL_SemValue(sem) < DESTROYED_SEMVALUE) |
|---|
| 165 | SDL_SemPost(sem); |
|---|
| 166 | return r; |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | /** |
|---|
| 170 | * Indicate to the thread that called wants_to_destroy() that X is |
|---|
| 171 | * now ready to destroy |
|---|
| 172 | */ |
|---|
| 173 | void ready_to_destroy() { |
|---|
| 174 | stop(); |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | /** |
|---|
| 178 | * Return true if this sem has been destroyed |
|---|
| 179 | */ |
|---|
| 180 | bool hasDestroyed() { |
|---|
| 181 | return !run || SDL_SemValue(sem) >= DESTROYED_SEMVALUE; |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | /** Return the value of the semaphore */ |
|---|
| 185 | Uint32 value() { |
|---|
| 186 | return SDL_SemValue(sem); |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | /** |
|---|
| 190 | * Cause wants_to_destroy to block -- the alternate / thr has started |
|---|
| 191 | */ |
|---|
| 192 | bool alternate_started() { |
|---|
| 193 | return wait(); |
|---|
| 194 | } |
|---|
| 195 | |
|---|
| 196 | /** Return true if we are running */ |
|---|
| 197 | bool running() const {return SemWaiter::running();} |
|---|
| 198 | void cancel() {SemWaiter::cancel();} |
|---|
| 199 | }; |
|---|
| 200 | |
|---|
| 201 | #endif |
|---|