(svn r9419) [NoAI] -Codechange: support AI threads also on Win32 (using threads on Win95 and fibers on other Win32 platforms)
--- a/projects/openttd.vcproj Thu Mar 22 23:12:05 2007 +0000
+++ b/projects/openttd.vcproj Fri Mar 23 21:22:28 2007 +0000
@@ -1226,6 +1226,9 @@
RelativePath=".\..\src\misc\blob.hpp">
</File>
<File
+ RelativePath=".\..\src\misc\countedobj.cpp">
+ </File>
+ <File
RelativePath=".\..\src\misc\countedptr.hpp">
</File>
<File
--- a/projects/openttd_vs80.vcproj Thu Mar 22 23:12:05 2007 +0000
+++ b/projects/openttd_vs80.vcproj Fri Mar 23 21:22:28 2007 +0000
@@ -1848,6 +1848,10 @@
>
</File>
<File
+ RelativePath=".\..\src\misc\countedobj.cpp"
+ >
+ </File>
+ <File
RelativePath=".\..\src\misc\countedptr.hpp"
>
</File>
--- a/source.list Thu Mar 22 23:12:05 2007 +0000
+++ b/source.list Fri Mar 23 21:22:28 2007 +0000
@@ -383,6 +383,7 @@
misc/autoptr.hpp
misc/binaryheap.hpp
misc/blob.hpp
+misc/countedobj.cpp
misc/countedptr.hpp
misc/crc32.hpp
misc/fixedsizearray.hpp
--- a/src/ai/ai_threads.cpp Thu Mar 22 23:12:05 2007 +0000
+++ b/src/ai/ai_threads.cpp Fri Mar 23 21:22:28 2007 +0000
@@ -12,12 +12,13 @@
#include "ai_threads.h"
#include "api/ai_controller.hpp"
#include "api/ai_object.hpp"
-
-#include <pthread.h>
+#include "../misc/autoptr.hpp"
+#include "../misc/countedptr.hpp"
+#include <map>
-/** The overall state of an AI threading wise */
-class AIThreadState {
- /** Different states of the AI thread */
+class AIThread : public SimpleCountedObject {
+public:
+ /** Different states of the AI thread. */
enum ThreadState {
INITIALIZED, ///< The mutex and conditions are initialized
STARTING, ///< The thread is just started
@@ -27,43 +28,48 @@
STOPPED, ///< The main AI has stopped
};
- ThreadState state; ///< The current state of the AI
- OTTDThread *thread; ///< The thread of the AI
- pthread_mutex_t mutex; ///< Synchronization mutex
-
- pthread_cond_t ai_cont; ///< Signal to let the AI continue
- pthread_cond_t main_cont; ///< Signal to let the main continue
+ static const int MAIN_THREAD = -1;
- PlayerID player_id; ///< The ID of the current player
- int ticks_to_sleep; ///< Sleep this many runticks
- AIController *controller; ///< Controller of this AI
+protected:
+ typedef std::map<int, CCountedPtr<AIThread> > Threads;
+ static Threads s_threads; ///< collection of active fibers
+
+ int fiber_id; ///< id of this fiber (or MAIN_THREAD for the main) - equal to the ID of the current player
+ ThreadState state; ///< The current state of the AI
+ int ticks_to_sleep; ///< Sleep this many runticks
+ AIController *controller; ///< Controller of this AI
public:
- bool last_command_res; ///< The result of the last
-
- /** Basic initialisation; real initialisation is done in Initialise. */
- AIThreadState() : state(STOPPED) {}
+ bool last_command_res; ///< The result of the last command
/**
- * Initialize this AI Thread State for the given controller.
- * @param player the player ID of this AI
- * @param controller the AI to run
- * @note should be called before any other function.
+ * Initialize this AI Thread base class for the given controller.
+ * @param player the player ID of this AI (used as fiber_id)
+ * @param controller the AI have to run
+ * @note this constructor is protected. It should be called only by
+ * subclass (OS specific thread implementation).
*/
- void Initialise(PlayerID player, AIController *controller)
+protected:
+ AIThread(PlayerID player, AIController *controller)
{
- assert(this->state == STOPPED);
- this->thread = NULL;
- this->state = INITIALIZED;
+ /* register new thread */
+ s_threads[player] = this;
- pthread_mutex_init(&this->mutex, NULL);
- pthread_cond_init(&this->ai_cont, NULL);
- pthread_cond_init(&this->main_cont, NULL);
+ /* ensure that main thread has its wrapper too */
+ if (player != MAIN_THREAD) stFind(MAIN_THREAD);
- this->player_id = player;
- this->ticks_to_sleep = 0;
+ this->state = INITIALIZED;
+ this->fiber_id = player;
+ this->ticks_to_sleep = 0;
+ this->controller = controller;
this->last_command_res = false;
- this->controller = controller;
+ }
+
+public:
+ virtual ~AIThread()
+ {
+ /* at this point we should be already removed from the thread collection */
+ assert(this->fiber_id == MAIN_THREAD || stFind(this->fiber_id) == NULL);
}
/**
@@ -71,54 +77,29 @@
* any other suspending command.
* @note should be called after Initialize, but before any other function.
*/
- void Start()
- {
- assert(this->state == INITIALIZED);
- pthread_mutex_lock(&this->mutex);
-
- this->state = STARTING;
- this->thread = OTTDCreateThread(&AIThreadState::stThreadRun, this);
-
- /* XXX -- We should handle this more nicely */
- assert(this->thread != NULL);
-
- pthread_cond_wait(&this->main_cont, &this->mutex);
- pthread_mutex_unlock(&this->mutex);
- }
-
- /**
- * Function that is started as process of the new thread.
- * @param arg some random argument, in this case a AIThreadState
- */
- static void *stThreadRun(void *arg)
- {
- ((AIThreadState*)arg)->ThreadRun();
- return NULL;
- }
+public:
+ virtual bool Start() = 0;
/**
* The method that runs in the new Thread.
* @note should be called by the newly spawned thread in Start.
*/
- void ThreadRun()
+protected:
+ void ThreadProc()
{
assert(this->state == STARTING);
+ this->state = RUNNING;
- this->state = RUNNING;
- pthread_mutex_lock(&this->mutex);
this->controller->Start();
if (this->state != STOPPING) {
- DEBUG(ai, 1, "We've got a suicidal AI for player %d", this->player_id);
+ DEBUG(ai, 1, "We've got a suicidal AI for player %d", this->fiber_id);
/* The AI stopped on it's own */
this->state = STOPPED;
- AI_PlayerDied(this->player_id);
+ AI_PlayerDied((PlayerID)this->fiber_id);
} else {
this->state = STOPPED;
}
-
- pthread_cond_signal(&this->main_cont);
- pthread_mutex_unlock(&this->mutex);
}
/**
@@ -126,44 +107,61 @@
* @param timeout time to suspend. < 0 means infinite (MultiPlayer only!)
* @note should be called from the AI thread.
*/
+public:
void Suspend(int timeout)
{
- assert(this->state == RUNNING);
+ assert(this->fiber_id != MAIN_THREAD); // should not attempt to suspend main fiber
+ assert(this->state == RUNNING); // must be in running state
+ assert(this->CurrentThread() == this); // we can only suspend self
- this->state = SUSPENDED;
this->ticks_to_sleep = timeout;
- pthread_cond_signal(&this->main_cont);
- /* Wait till we are resurrected */
- pthread_cond_wait(&this->ai_cont, &this->mutex);
- this->state = RUNNING;
+ /* when AI thread gets suspended, it switches always to main thread */
+ AIThread *main = stFind(MAIN_THREAD);
+ main->SwitchToThis(RUNNING);
+ if (this->state == STOPPING) {
+ this->controller->Stop();
+ OnStop();
+ //throw std::exception();
+ //NOT_REACHED();
+ return;
+ }
+ assert(this->state == RUNNING);
}
/**
* Set the AI thread to resume at the next call of RunTick.
* @note should NOT be called from the AI thread.
*/
+public:
void Resume()
{
assert(this->state == SUSPENDED);
/* Normally the ticks_to_sleep hangs at -1 for MP. Possible the MP is
- * faster then the delay requested by the user. In this case the value
- * is lower. To let the normal delay system kick in, we reverse the value
- * of ticks_to_sleep. But now it doesn't directly continue when the value
- * was 'hanging', so we substract 1 and it all works fine. */
+ * faster then the delay requested by the user. In this case the value
+ * is lower. To let the normal delay system kick in, we reverse the value
+ * of ticks_to_sleep. But now it doesn't directly continue when the value
+ * was 'hanging', so we subtract 1 and it all works fine. */
this->ticks_to_sleep = -this->ticks_to_sleep - 1;
}
/**
* Let the AI thread run for a while and return when it is done.
- * However, when the thread is suspened and the suspend timeout
+ * However, when the thread is suspended and the suspend timeout
* has not yet passed, nothing happens except decrementing the
* before mentioned timeout.
* @note should NOT be called from the AI thread.
*/
+public:
void RunTick()
{
+ /* should be called from main fiber only */
+ assert(CurrentThread() == stFind(MAIN_THREAD));
+ /* only AI fibers should be resumed this way */
+ assert(this != stFind(MAIN_THREAD));
+ /* AI fiber should be still suspended */
assert(this->state == SUSPENDED);
+
this->controller->IncreaseTick();
/* If the value is < -1, the user wants a delay which might exceed the delay
@@ -175,46 +173,636 @@
if (this->ticks_to_sleep < 0) return; // We have to wait infinitely
if (--this->ticks_to_sleep > 0) return; // We have to wait a little longer
- this->state = RUNNING;
-
- /* Resurect the AI thread */
- pthread_mutex_lock(&this->mutex);
- pthread_cond_signal(&this->ai_cont);
-
- /* Wait till the AI thread has finished */
- pthread_cond_wait(&this->main_cont, &this->mutex);
- pthread_mutex_unlock(&this->mutex);
+ this->SwitchToThis(RUNNING);
}
/**
* Stop the AI thread and wait till it is stopped.
* @note should NOT be called from the AI thread.
*/
- void Stop()
- {
- /* The AI stopped itself */
- if (this->state == STOPPED) return;
-
- assert(this->state == SUSPENDED);
+public:
+ virtual void Stop() = 0;
- this->state = STOPPING;
- this->controller->Stop();
+ /**
+ * Called before the AI thread finishes. Should wakeup the main thread.
+ */
+public:
+ virtual void OnStop() {};
- /* Resurect the AI thread */
- pthread_mutex_lock(&this->mutex);
- pthread_cond_signal(&this->ai_cont);
- pthread_mutex_unlock(&this->mutex);
+ /**
+ * Called in the context of thread that wants to yield execution and switch
+ * to 'this' thread. One of those threads should be always the main thread
+ * (fiber_id == MAIN_THREAD).
+ */
+public:
+ virtual void SwitchToThis(ThreadState new_state) = 0;
- /* Wait till the AI thread has finished */
- OTTDJoinThread(this->thread);
- this->state = STOPPED;
+ /**
+ * Find and return the thread object by its 'id' (player_id or MAIN_THREAD). Returns
+ * NULL if such thread object doesn't exist. If fiber_id == MAIN_THREAD and the thread
+ * object with this id doesn't exist, the new one is created and attached to the current
+ * (main) thread.
+ */
+public:
+ static AIThread* stFind(int fiber_id)
+ {
+ Threads::iterator it = s_threads.find(fiber_id);
+ if (it == s_threads.end()) {
+ if (fiber_id != MAIN_THREAD) return NULL;
+ /* main thread doesn't have its own thread object, create it */
+ return stNew((PlayerID)MAIN_THREAD, NULL);
+ }
+ return (*it).second;
+ }
+
+ /**
+ * Remove thread object from the collection by its 'id' and destroy it.
+ */
+public:
+ static void stRelease(int fiber_id)
+ {
+ Threads::iterator it = s_threads.find(fiber_id);
+ if (it != s_threads.end()) s_threads.erase(it);
+ }
+
+ /**
+ * Find the thread object that belongs to the currently running thread (caller).
+ */
+public:
+ static AIThread* stCurrentThread()
+ {
+ AIThread *main = stFind(MAIN_THREAD);
+ AIThread *cur = main->CurrentThread();
+ return cur;
+ }
+
+ /**
+ * Find the thread object that belongs to the currently running thread (caller).
+ */
+public:
+ virtual AIThread* CurrentThread() = 0;
+
+ /**
+ * Create new AI thread object for given player and add it into thread collection.
+ */
+public:
+ static AIThread* stNew(PlayerID player, AIController *controller);
+};
+
+/**
+ * Collection of all AI Thread objects.
+ */
+/* static */ AIThread::Threads AIThread::s_threads;
+
+
+
+/**
+ * AI thread implementation using Win32 thread API. This should be used only on Win95
+ * where Fiber API is not supported. Main OTTD thread has assigned the first object
+ * in collection (AIThread::s_threads[MAIN_THREAD]). Other threads created by this main
+ * thread have assigned higher slots [PlayerID]. This implementation is supposed to emulate
+ * Win32 fibers using normal Win32 threads. Synchronisation is done using simple event
+ * object (kind of bi-state semaphore gate - 0/1 - which is reset to 0 when thread passes
+ * the wait function and must be set back to 1 in order to fire another thread).
+ * Each thread from the collection (except one) is wating on its own event object. So the
+ * thread that wants to yeald execution must set the event object of the target thread
+ * and then enter its own wait state.
+ */
+class AIThread_MT : public AIThread {
+ static int s_current; ///< currently executed thread
+
+ AutoPtrT<AutoResetEvent> evt_go; ///< Win32 event handle that signals the thread can go
+ ThreadState next_state; ///< Next state after wakeup
+ AutoPtrT<ThreadObject> thr; ///< Thread Object associated with this AI thread
+
+ /**
+ * Initialize this AI Thread Object for the given controller.
+ * @param player the player ID of this AI (used as fiber_id) or MAIN_THREAD if attaching to the main game thread
+ * @param controller the AI have to run
+ */
+public:
+ AIThread_MT(PlayerID player, AIController *controller)
+ : AIThread(player, controller)
+ {
+ DEBUG(ai, 3, "+AIThread_MT(%d) from thr %d", player, ThreadObject::CurrentId());
+ this->evt_go = AutoResetEvent::New();
+ this->next_state = STOPPED;
+
+ /* Handle main thread differently. When AIThread::AIThread() calls stFind() the new Thread Object
+ * is created for the main thread. In this case we will not create new system thread but rather
+ * attach to the curent one. */
+ if (player == MAIN_THREAD) {
+ /* also main thread needs to know own win32 thread id */
+ this->thr = ThreadObject::Current();
+ } else {
+ /* create new Thread Object */
+ this->thr = ThreadObject::New();
+ }
+ }
+
+ /**
+ * Destructor (AI Thread Object cleanup).
+ */
+public:
+ /*virtual*/ ~AIThread_MT()
+ {
+ DEBUG(ai, 3, "-AIThread_MT(%d) from thr %u", this->fiber_id, ThreadObject::CurrentId());
+ bool wait_ok = this->thr->IsCurrent() ? true : this->thr->WaitForStop();
+ assert(wait_ok);
+ this->evt_go.Release();
+ }
+
+ /**
+ * Implementation specific Start() routine.
+ * @see AIThread::Start()
+ */
+public:
+ virtual bool Start()
+ {
+ AIThread_MT *cur = stCurrentFiber();
+ assert(cur != this); // target fiber is not active already
+ assert(!this->thr->IsRunning()); // not yet started
+ assert(fiber_id != MAIN_THREAD); // main fiber can't be started this way
+
+ cur->state = SUSPENDED;
+ this->next_state = STARTING;
+
+ {
+ /* Protect the AI Thread Object against sudden death in the case when
+ * AI decides to stop itself by calling AI_StopPlayer(). Technically there
+ * is no problem deleting the the AI Thread Object from its thread, but for
+ * the code robustness there is assert(m_attached || !IsRunning()); to avoid
+ * that. */
+ CCountedPtr<AIThread> protect_this = this;
+ if (!this->thr->Begin(&stThreadProc, this)) return false; // unable to create thread
+ cur->EnterWaitState();
+ } // end of 'protection area'
+
+ return true;
+ }
+
+ /**
+ * Function that is started as process of the new AI thread (fiber).
+ * @param fiber AIThread_Fiber* to the fiber that starts
+ */
+protected:
+ static void CDECL stThreadProc(void *thr)
+ {
+ AIThread_MT *cur = (AIThread_MT*)thr;
+ cur->FiberProc();
+ }
+
+ /**
+ * The method that runs in the context of new thread (virtual fiber) when started.
+ */
+protected:
+ void FiberProc()
+ {
+ try
+ {
+ s_current = this->fiber_id;
+ this->state = this->next_state;
+ AIThread::ThreadProc();
+ }
+ catch (std::exception &e)
+ {
+ DEBUG(ai, 0, "%s", e.what());
+ }
+ DEBUG(ai, 3, "thread %d finished", this->fiber_id);
+ }
+
+ /**
+ * Implementation specific way how to stop execution of the AI thread.
+ * @see AIThread::Stop()
+ */
+public:
+ /*virtual*/ void Stop()
+ {
+ AIThread_MT *cur = stCurrentFiber();
+ /* The AI stopping itself */
+ if (cur == this) {
+ /* must be AI fiber */
+ assert(this->fiber_id != MAIN_THREAD);
+
+ DEBUG(ai, 3, "this->OnStop(%d) from thr %d", this->fiber_id, ThreadObject::CurrentId());
+ this->state = STOPPING;
+ this->controller->Stop();
+ this->OnStop();
+ //throw std::exception();
+ //NOT_REACHED();
+ return;
+ }
+
+ assert(cur->fiber_id == MAIN_THREAD);
+ assert(this->state == SUSPENDED);
+ this->SwitchToThis(STOPPING);
+ }
+
+ /**
+ * This routine should notify main thread that AI thread finished (stopped
+ * forever).
+ */
+protected:
+ /*virtual*/ void OnStop()
+ {
+ AIThread_MT *main = (AIThread_MT*)stFind(MAIN_THREAD);
+ main->evt_go->Set();
+ }
+
+ /**
+ * Implementation specific way how to yield execution to the other thread (virtual fiber).
+ * @see AIThread::SwitchToThis()
+ */
+protected:
+ void SwitchToThis(ThreadState new_state)
+ {
+ AIThread_MT *cur = stCurrentFiber();
+ assert(cur != this); // target fiber is not active already
+ assert(this->state == SUSPENDED); // target fiber is in proper state
+ assert(this->thr->IsRunning() || this->fiber_id == MAIN_THREAD); // target fiber is created
+
+ cur->state = SUSPENDED;
+ this->next_state = new_state;
+
+ {
+ /* Protect the AI Thread Object against sudden death in the case when
+ * AI decides to stop itself by calling AI_StopPlayer(). Technically there
+ * is no problem deleting the the AI Thread Object from its thread, but for
+ * the code robustness there is assert(m_attached || !IsRunning()); to avoid
+ * that. */
+ CCountedPtr<AIThread> protect_this = this;
+ this->evt_go->Set();
+ cur->EnterWaitState();
+ } // end of 'protection area'
+ }
+
+ /**
+ * Wait for the 'GO' signal. This way all thread except one are waiting on their own
+ * event object in order to emulate cooperative multithreading (fibers).
+ */
+protected:
+ void EnterWaitState()
+ {
+ assert(this->thr->IsCurrent()); // can wait on own event only
+ this->evt_go->Wait();
+ s_current = this->fiber_id;
+ assert(this->state == SUSPENDED);
+ this->state = this->next_state;
+ assert(this->state != SUSPENDED);
+ }
+
+ /**
+ * Ugly way how to get AI Thread object that belongs to the currently executed
+ * (calling) thread.
+ * @todo consider using TLS API or thread map to make it less ugly
+ */
+protected:
+ AIThread_MT* stCurrentFiber()
+ {
+ AIThread_MT *cur = (AIThread_MT*)stFind(s_current);
+ assert(cur != NULL);
+ assert(cur->thr->IsCurrent());
+ return cur;
+ }
+
+ /**
+ * Ugly way how to provide stCurrentFiber() functionality for the base class.
+ */
+protected:
+ /*virtual*/ AIThread* CurrentThread()
+ {
+ return stCurrentFiber();
}
};
/**
- * All AI Thread states.
+ * Ugly way how to get AI Thread object that belongs to the currently executed
+ * (calling) thread.
+ * @todo consider using TLS API or thread map to make it less ugly
*/
-static AIThreadState _ai_state[MAX_PLAYERS];
+ /* static */ int AIThread_MT::s_current = AIThread::MAIN_THREAD;
+
+#ifdef _WIN32
+
+#include <windows.h>
+#include <process.h>
+
+/**
+ * AI thread implementation using native Win32 fiber API. This should be preferred
+ * way for most of Win32 platforms - all except Win95 (this API was introduced on Win98).
+ * All fibers share the same Win32 thread but each fiber has its own stack and context.
+ * They are not preempted, cannot run in parallel and they must cooperatively switch
+ * (yield execution from one to another) in order to execute them all.
+ * One fiber has id == MAIN_THREAD. This fiber is main (belongs to the main OTTD thread).
+ * The main fiber is created using ConvertThreadToFiber() API so then it can create and
+ * execute other fibers. Fiber API allows to provide one void* as fiber data. We use it
+ * to hold pointer to the AI thread object (AIThread_Fiber*) it is related to and
+ * which controls the fiber execution state.
+ *
+ * This Win32 specific implementation can be made more generic using libpth on unix.
+ */
+class AIThread_Fiber : public AIThread {
+ DWORD id_thr; ///< Win32 thread id (used for assert only)
+ LPVOID fiber; ///< Win32 fiber
+
+public:
+ /**
+ * Initialize this AI Thread Object for the given controller.
+ * @param player the player ID of this AI (used as fiber_id)
+ * @param controller the AI have to run
+ */
+ AIThread_Fiber(PlayerID player, AIController *controller)
+ : AIThread(player, controller)
+ {
+ this->id_thr = GetCurrentThreadId();
+ this->fiber = NULL;
+ if (player == MAIN_THREAD) {
+ this->ConvertThreadToFiber();
+ assert(this->fiber != NULL);
+ }
+ }
+
+ /**
+ * Destructor (AI Thread Object cleanup).
+ */
+public:
+ /*virtual*/ ~AIThread_Fiber()
+ {
+ if (this->fiber_id == MAIN_THREAD) {
+ this->ConvertFiberToThread();
+ } else {
+ this->DeleteFiber();
+ }
+ }
+
+ /**
+ * Fiber specific Start() routine.
+ * @see AIThread::Start()
+ */
+public:
+ virtual bool Start()
+ {
+ assert(stCurrentFiber() != this); // target fiber is not active already
+ assert(this->fiber == NULL); // not yet started
+ assert(fiber_id != MAIN_THREAD); // main fiber can't be started this way
+
+ /* create fiber for this AI thread */
+ this->CreateFiber();
+ if (this->fiber == NULL) return false; // unable to create fiber
+
+ /* Set initial target state and switch to the target thread. This will start
+ * the other fiber execution by calling its FiberProc(). */
+ this->state = SUSPENDED; // initial state after creation going to wake up soon
+ this->SwitchToThis(STARTING);
+
+ return true; // indicate success
+ }
+
+ /**
+ * Fiber specific way how to stop execution of the AI thread.
+ * @see AIThread::Stop()
+ */
+public:
+ virtual void Stop()
+ {
+ AIThread_Fiber *cur = stCurrentFiber();
+ /* The AI stopping itself? */
+ if (cur == this) {
+ /* Yes, stopping itself. Must be AI fiber */
+ assert(this->fiber_id != MAIN_THREAD);
+
+ this->state = STOPPING;
+ this->controller->Stop();
+ //throw std::exception();
+ //NOT_REACHED();
+ return;
+ }
+
+ /* Main thread (fiber) stopping AI thread */
+ assert(cur->fiber_id == MAIN_THREAD);
+ assert(this->state == SUSPENDED);
+ this->SwitchToThis(STOPPING);
+
+ this->DeleteFiber();
+ }
+
+ /**
+ * Fiber specific way how to yield execution to the other fiber.
+ * @see AIThread::SwitchToThis()
+ */
+public:
+ void SwitchToThis(ThreadState new_state)
+ {
+ assert(stCurrentFiber() != this); // target fiber is not active already
+ assert(this->state == SUSPENDED); // target fiber is in proper state
+ assert(this->fiber != NULL); // target fiber is created
+
+ stCurrentFiber()->state = SUSPENDED;
+
+ {
+ /* Protect 'this' so it can't be deleted inside SwitchToFiber().
+ * It could happen if AI decides to stop itself by calling AI_StopPlayer().
+ * The problem is that AI Thread Object destructor calls DeleteFiber(). There
+ * is built-in limitation that you can't call DeleteFiber() to delete
+ * the current (running) fiber. If you do so, the thread exits and whole
+ * application ends. */
+ CCountedPtr<AIThread> protect_this = this;
+ this->state = new_state;
+ this->SwitchToFiber();
+ assert(this->state == SUSPENDED);
+ } // end of 'protection area'
+
+ stCurrentFiber()->state = RUNNING;
+ }
+
+ /**
+ * Get AI thread instance of the current (calling) fiber.
+ */
+public:
+ AIThread_Fiber* stCurrentFiber()
+ {
+ AIThread_Fiber *cur = (AIThread_Fiber*)::GetFiberData();
+ assert(cur != NULL);
+ return cur;
+ }
+
+ /**
+ * Ugly way how to provide stCurrentFiber() functionality for the base class.
+ */
+public:
+ /*virtual*/ AIThread* CurrentThread()
+ {
+ return stCurrentFiber();
+ }
+
+
+ /**
+ * Function that is started as process of the new AI thread (fiber).
+ * @param fiber AIThread_Fiber* to the fiber that starts
+ */
+protected:
+ static VOID CALLBACK stFiberProc(LPVOID fiber)
+ {
+ AIThread_Fiber *cur = (AIThread_Fiber*)fiber;
+ cur->FiberProc();
+ }
+
+ /**
+ * The method that runs in the context of new Fiber when started.
+ */
+protected:
+ void FiberProc()
+ {
+ try
+ {
+ AIThread::ThreadProc();
+ }
+ catch (std::exception &e)
+ {
+ DEBUG(ai, 0, "%s", e.what());
+ }
+ DEBUG(ai, 3, "fiber %d finished", this->fiber_id);
+ stFind(MAIN_THREAD)->SwitchToThis(RUNNING);
+ }
+
+ /**
+ * Simple wrapper of the LoadLibrary() and GetProcAddress() APIs that returns
+ * pointer to API function of the given name. The fiber specific APIs are retrieved
+ * this way (dynamically) instead of just linking to the kernel32.dll statically.
+ * Linking them statically would cause OTTD crash on startup due to the unsatisfied
+ * imports. In our case we just get NULL if function is not there.
+ */
+protected:
+ static FARPROC stGetProcAddr(const char *name)
+ {
+ static HMODULE hKernel = LoadLibraryA("kernel32.dll");
+ return GetProcAddress(hKernel, name);
+ }
+
+ /**
+ * Dynamic wrapper of ConvertThreadToFiber() Win32 API function.
+ * @see stGetProcAddr
+ */
+protected:
+ void ConvertThreadToFiber()
+ {
+ assert(this->fiber_id == MAIN_THREAD);
+ typedef LPVOID (WINAPI *FnConvertThreadToFiber)(LPVOID lpParameter);
+ static FnConvertThreadToFiber fnConvertThreadToFiber = (FnConvertThreadToFiber)stGetProcAddr("ConvertThreadToFiber");
+ assert(fnConvertThreadToFiber != NULL);
+ this->fiber = fnConvertThreadToFiber(this);
+ }
+
+ /**
+ * Dynamic wrapper of CreateFiber() Win32 API function.
+ * @see stGetProcAddr
+ */
+protected:
+ void CreateFiber()
+ {
+ assert(this->fiber_id != MAIN_THREAD);
+ typedef LPVOID (WINAPI *FnCreateFiber)(SIZE_T dwStackSize, LPFIBER_START_ROUTINE lpStartAddress, LPVOID lpParameter);
+ static FnCreateFiber fnCreateFiber = (FnCreateFiber)stGetProcAddr("CreateFiber");
+ assert(fnCreateFiber != NULL);
+ this->fiber = fnCreateFiber(0, &stFiberProc, this);
+ }
+
+ /**
+ * Dynamic wrapper of DeleteFiber() Win32 API function.
+ * @see stGetProcAddr
+ */
+protected:
+ void DeleteFiber()
+ {
+ assert(this->fiber_id != MAIN_THREAD);
+ assert(this != stCurrentFiber());
+ typedef VOID (WINAPI * FnDeleteFiber)(LPVOID lpFiber);
+ static FnDeleteFiber fnDeleteFiber = (FnDeleteFiber)stGetProcAddr("DeleteFiber");
+ assert(fnDeleteFiber != NULL);
+ fnDeleteFiber(this->fiber);
+ this->fiber = NULL;
+ }
+
+ /**
+ * Dynamic wrapper of ConvertFiberToThread() Win32 API function.
+ * @see stGetProcAddr
+ */
+protected:
+ void ConvertFiberToThread()
+ {
+ assert(this->fiber_id == MAIN_THREAD);
+ typedef BOOL (WINAPI *FnConvertFiberToThread)();
+ static FnConvertFiberToThread fnConvertFiberToThread = (FnConvertFiberToThread)stGetProcAddr("ConvertFiberToThread");
+ assert(fnConvertFiberToThread != NULL);
+ fnConvertFiberToThread();
+ this->fiber = NULL;
+ }
+
+ /**
+ * Dynamic wrapper of SwitchToFiber() Win32 API function.
+ * @see stGetProcAddr
+ */
+protected:
+ void SwitchToFiber()
+ {
+ assert(this->fiber != NULL);
+ typedef VOID (WINAPI *FnSwitchToFiber)(LPVOID fiber);
+ static FnSwitchToFiber fnSwitchToFiber = (FnSwitchToFiber)stGetProcAddr("SwitchToFiber");
+ assert(fnSwitchToFiber != NULL);
+ fnSwitchToFiber(this->fiber);
+ }
+
+ /**
+ * Returns true if Win32 fiber API is supported.
+ * @see stGetProcAddr
+ */
+public:
+ static bool IsSupported()
+ {
+ static bool first_run = true;
+ static bool is_supported = false;
+ if (first_run) {
+ first_run = false;
+ static const char *names[] = {
+ "ConvertThreadToFiber",
+ "CreateFiber",
+ "DeleteFiber",
+ "ConvertFiberToThread",
+ "SwitchToFiber"};
+ for (size_t i = 0; i < lengthof(names); i++) {
+ if (stGetProcAddr(names[i]) == NULL) return false;
+ }
+ is_supported = true;
+ }
+ return is_supported;
+ }
+};
+
+/**
+ * Create, register and return the new AI Thread object. It should choose the best
+ * implementation for the current platform.
+ */
+/* static */ AIThread* AIThread::stNew(PlayerID player, AIController *controller)
+{
+ if (AIThread_Fiber::IsSupported()) {
+ /* Fibers are supported, use them */
+ return new AIThread_Fiber(player, controller);
+ }
+ /* Fibers are not supported (Win95?) */
+ return new AIThread_MT(player, controller);
+}
+
+#else /*_WIN32*/
+
+/**
+ * Create, register and return the new AI Thread object.
+ */
+/* static */ AIThread* AIThread::stNew(PlayerID player, AIController *controller)
+{
+ return new AIThread_MT(player, controller);
+}
+
+#endif /*!_WIN32*/
/**
@@ -225,7 +813,10 @@
*/
void AI_SuspendPlayer(PlayerID player, int timeout)
{
- _ai_state[player].Suspend(timeout);
+ DEBUG(ai, 5, "AI_SuspendPlayer(%d, %d) from thr %d", player, timeout, ThreadObject::CurrentId());
+ AIThread *thr = AIThread::stCurrentThread();
+ assert(thr != NULL);
+ thr->Suspend(timeout);
}
/**
@@ -234,7 +825,13 @@
*/
void AI_RunTick(PlayerID player)
{
- _ai_state[player].RunTick();
+ DEBUG(ai, 6, "AI_RunTick(%d) from thr %d", player, ThreadObject::CurrentId());
+ AIThread *thr = AIThread::stFind(player);
+ if (thr == NULL) {
+ DEBUG(ai, 0, "AI_RunTick() called for dead AI player #%d", player);
+ return;
+ }
+ thr->RunTick();
}
/**
@@ -244,8 +841,11 @@
*/
void AI_StartPlayer(PlayerID player, AIController *controller)
{
- _ai_state[player].Initialise(player, controller);
- _ai_state[player].Start();
+ DEBUG(ai, 3, "AI_StartPlayer(%d) from thr %d", player, ThreadObject::CurrentId());
+ AIThread *thr = AIThread::stFind(player);
+ assert(thr == NULL);
+ thr = AIThread::stNew(player, controller);
+ thr->Start();
}
/**
@@ -254,7 +854,11 @@
*/
void AI_StopPlayer(PlayerID player)
{
- _ai_state[player].Stop();
+ DEBUG(ai, 3, "AI_StopPlayer(%d) from thr %d", player, ThreadObject::CurrentId());
+ AIThread *thr = AIThread::stFind(player);
+ if (thr == NULL) return;
+ thr->Stop();
+ AIThread::stRelease(player);
}
/**
@@ -263,13 +867,16 @@
*/
void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2)
{
+ DEBUG(ai, 6, "CcAI(%d) from thr %d", _current_player, ThreadObject::CurrentId());
+ AIThread *thr = AIThread::stFind(_current_player);
+ assert(thr != NULL);
/* Store if we were a success or not and resume */
- _ai_state[_current_player].last_command_res = success;
+ thr->last_command_res = success;
/* Store some values inside the AIObject static memory */
AIObject::SetNewVehicleID(_new_vehicle_id);
- _ai_state[_current_player].Resume();
+ thr->Resume();
}
/**
@@ -280,5 +887,8 @@
*/
bool AI_GetCallbackResult(PlayerID player)
{
- return _ai_state[player].last_command_res;
+ DEBUG(ai, 6, "AI_GetCallbackResult(%d) from thr %d", player, ThreadObject::CurrentId());
+ AIThread *thr = AIThread::stFind(player);
+ assert(thr != NULL);
+ return thr->last_command_res;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/misc/countedobj.cpp Fri Mar 23 21:22:28 2007 +0000
@@ -0,0 +1,22 @@
+/* $Id$ */
+
+#include "../stdafx.h"
+
+#include "countedptr.hpp"
+
+int32 SimpleCountedObject::AddRef()
+{
+ return ++m_ref_cnt;
+}
+
+int32 SimpleCountedObject::Release()
+{
+ int32 res = --m_ref_cnt;
+ assert(res >= 0);
+ if (res == 0) {
+ FinalRelease();
+ delete this;
+ }
+ return res;
+}
+
--- a/src/misc/countedptr.hpp Thu Mar 22 23:12:05 2007 +0000
+++ b/src/misc/countedptr.hpp Fri Mar 23 21:22:28 2007 +0000
@@ -3,7 +3,7 @@
#ifndef COUNTEDPTR_HPP
#define COUNTEDPTR_HPP
-#if 0 // reenable when needed
+#if 1 // reenable when needed
/** @file CCountedPtr - smart pointer implementation */
/** CCountedPtr - simple reference counting smart pointer.
@@ -42,7 +42,7 @@
public:
/** release smart pointer (and decrement ref count) if not null */
- FORCEINLINE void Release() {if (m_pT != NULL) {m_pT->Release(); m_pT = NULL;}}
+ FORCEINLINE void Release() {if (m_pT != NULL) {Tcls* pT = m_pT; m_pT = NULL; pT->Release();}}
/** dereference of smart pointer - const way */
FORCEINLINE const Tcls* operator -> () const {assert(m_pT != NULL); return m_pT;};
@@ -54,7 +54,7 @@
FORCEINLINE operator const Tcls*() const {assert(m_pT == NULL); return m_pT;}
/** raw pointer casting operator - non-const way */
- FORCEINLINE operator Tcls*() {assert(m_pT == NULL); return m_pT;}
+ FORCEINLINE operator Tcls*() {return m_pT;}
/** operator & to support output arguments */
FORCEINLINE Tcls** operator &() {assert(m_pT == NULL); return &m_pT;}
@@ -63,7 +63,7 @@
FORCEINLINE CCountedPtr& operator = (Tcls* pT) {Assign(pT); return *this;}
/** assignment operator from another smart ptr */
- FORCEINLINE CCountedPtr& operator = (CCountedPtr& src) {Assign(src.m_pT); return *this;}
+ FORCEINLINE CCountedPtr& operator = (const CCountedPtr& src) {Assign(src.m_pT); return *this;}
/** assignment operator helper */
FORCEINLINE void Assign(Tcls* pT);
@@ -72,10 +72,10 @@
FORCEINLINE bool IsNull() const {return m_pT == NULL;}
/** another way how to test for NULL value */
- FORCEINLINE bool operator == (const CCountedPtr& sp) const {return m_pT == sp.m_pT;}
+ //FORCEINLINE bool operator == (const CCountedPtr& sp) const {return m_pT == sp.m_pT;}
/** yet another way how to test for NULL value */
- FORCEINLINE bool operator != (const CCountedPtr& sp) const {return m_pT != sp.m_pT;}
+ //FORCEINLINE bool operator != (const CCountedPtr& sp) const {return m_pT != sp.m_pT;}
/** assign pointer w/o incrementing ref count */
FORCEINLINE void Attach(Tcls* pT) {Release(); m_pT = pT;}
@@ -96,5 +96,65 @@
}
}
+/**
+ * Adapter wrapper for CCountedPtr like classes that can't be used directly by stl
+ * collections as item type. For example CCountedPtr has overloaded operator & which
+ * prevents using CCountedPtr in stl collections (i.e. std::list<CCountedPtr<MyType> >)
+ */
+template <class T> struct AdaptT {
+ T m_t;
+
+ /** construct by wrapping the given object */
+ AdaptT(const T &t)
+ : m_t(t)
+ {}
+
+ /** assignment operator */
+ T& operator = (const T &t)
+ {
+ m_t = t;
+ return t;
+ }
+
+ /** type-cast operator (used when AdaptT is used instead of T) */
+ operator T& ()
+ {
+ return m_t;
+ }
+
+ /** const type-cast operator (used when AdaptT is used instead of const T) */
+ operator const T& () const
+ {
+ return m_t;
+ }
+};
+
+
+/** Simple counted object. Use it as base of your struct/class if you want to use
+ * basic reference counting. Your struct/class will destroy and free itself when
+ * last reference to it is released (using Relese() method). The initial reference
+ * count (when it is created) is zero (don't forget AddRef() at least one time if
+ * not using CCountedPtr<T>.
+ *
+ * @see misc/countedobj.cpp for implementation.
+ */
+struct SimpleCountedObject {
+ int32 m_ref_cnt;
+
+ SimpleCountedObject()
+ : m_ref_cnt(0)
+ {}
+
+ virtual ~SimpleCountedObject()
+ {};
+
+ virtual int32 AddRef();
+ virtual int32 Release();
+ virtual void FinalRelease() {};
+};
+
+
+
+
#endif /* 0 */
#endif /* COUNTEDPTR_HPP */
--- a/src/thread.cpp Thu Mar 22 23:12:05 2007 +0000
+++ b/src/thread.cpp Fri Mar 23 21:22:28 2007 +0000
@@ -2,6 +2,7 @@
#include "stdafx.h"
#include "thread.h"
+#include "debug.h"
#include <stdlib.h>
#include "helpers.hpp"
@@ -66,6 +67,8 @@
#elif defined(UNIX)
#include <pthread.h>
+#include <semaphore.h>
+#include <unistd.h>
struct OTTDThread {
pthread_t thread;
@@ -101,18 +104,175 @@
pthread_exit(NULL);
}
+
+/**
+ * C++ thread wrapper - posix pthread version
+ */
+struct ThreadObject_pthread : public ThreadObject {
+ pthread_t m_thr; ///< system thread identifier
+ void (CDECL *m_proc)(void*); ///< external thread procedure
+ void *m_param; ///< parameter for the external thread procedure
+ bool m_attached; ///< true if the ThreadObject was attached to an existing thread
+ sem_t m_sem_start; ///< here the new thread waits before it starts
+ sem_t m_sem_stop; ///< here the other thread can wait for this thread to end
+
+ ThreadObject_pthread()
+ : m_thr(0)
+ , m_proc(NULL)
+ , m_param(NULL)
+ , m_attached(false)
+ {
+ sem_init(&m_sem_start, 0, 0);
+ sem_init(&m_sem_stop, 0, 0);
+ }
+
+ /** Virtual destructor to allow 'delete' operator to work properly. */
+ /*virtual*/ ~ThreadObject_pthread()
+ {
+ sem_destroy(&m_sem_stop);
+ sem_destroy(&m_sem_start);
+ };
+
+ /** Returns true if thread is running. */
+ /*virtual*/ bool IsRunning()
+ {
+ return m_thr != 0;
+ }
+
+ /** Waits for the thread exit. Returns true if thread exited. */
+ /*virtual*/ bool WaitForStop()
+ {
+ assert(!IsCurrent());
+ int ret = sem_wait(&m_sem_stop);
+ if (ret == 0) {
+ /* we have passed semaphore so increment it again */
+ sem_post(&m_sem_stop);
+ return true;
+ }
+ return false;
+ }
+
+ /** Returns true if this Thread Object belongs to the current (calling) thread. */
+ /*virtual*/ bool IsCurrent()
+ {
+ return pthread_self() == m_thr;
+ }
+
+ /** Returns thread id of the thread associated to this Thread Object. */
+ /*virtual*/ uint Id()
+ {
+ return (uint)m_thr;
+ }
+
+ /** Begin thread execution by calling given procedure and passing given parameter to it. */
+ /*virtual*/ bool Begin(void (CDECL *proc)(void*), void *param)
+ {
+ m_proc = proc;
+ m_param = param;
+ pthread_create(&m_thr, NULL, &stThreadProc, this);
+ sem_post(&m_sem_start);
+ return true;
+ }
+
+ /** Thread procedure that is called by new thread. */
+ static void* stThreadProc(void *thr)
+ {
+ return ((ThreadObject_pthread*)thr)->ThreadProc();
+ }
+
+ /** Thread method that is called by new thread. */
+ void* ThreadProc()
+ {
+ /* the new thread stops here so the calling thread can complete pthread_create() call */
+ sem_wait(&m_sem_start);
+ try {
+ m_proc(m_param);
+ } catch (...) {
+ DEBUG(misc, 0, "Exception in thread %u!", Id());
+ }
+ m_thr = 0;
+ /* notify threads waiting for our completion */
+ sem_post(&m_sem_stop);
+ return NULL;
+ }
+};
+
+/** Used to construct new instance of thread object. */
+/*static*/ ThreadObject* ThreadObject::New()
+{
+ ThreadObject_pthread *thr = new ThreadObject_pthread();
+ return thr;
+}
+
+/** Returns thread object instance that belongs to the current (calling) thread. */
+/*static*/ ThreadObject* ThreadObject::Current()
+{
+ ThreadObject_pthread *thr = new ThreadObject_pthread();
+ thr->m_thr = pthread_self();
+ thr->m_attached = true;
+ return thr;
+}
+
+/** Returns thread id of the current (calling) thread. */
+/*static*/ uint ThreadObject::CurrentId()
+{
+ return (uint)pthread_self();
+}
+
+
+/**
+ * Event object that is signaled manually by Set() and reset automatically when thread passes Wait().
+ */
+struct AutoResetEvent_pthread : public AutoResetEvent {
+ sem_t m_sem;
+
+ AutoResetEvent_pthread()
+ {
+ sem_init(&m_sem, 0, 0);
+ }
+
+ /** Virtual destructor to allow 'delete' operator to work properly. */
+ /*virtual*/ ~AutoResetEvent_pthread()
+ {
+ sem_destroy(&m_sem);
+ }
+
+ /** Set the event state to signaled so that one thread can pass the Wait() method. */
+ /*virtual*/ void Set()
+ {
+ int val = 0;
+ if (sem_getvalue(&m_sem, &val) == 0 && val == 0) sem_post(&m_sem);
+ }
+
+ /** Wait until event is signaled by calling Set() */
+ /*virtual*/ void Wait()
+ {
+ sem_wait(&m_sem);
+ }
+
+};
+
+/** Used to construct new instance of event object. */
+/*static*/ AutoResetEvent* AutoResetEvent::New()
+{
+ AutoResetEvent_pthread *evt = new AutoResetEvent_pthread();
+ return evt;
+}
+
#elif defined(WIN32)
#include <windows.h>
+#include <process.h>
struct OTTDThread {
HANDLE thread;
+ uint thread_id;
OTTDThreadFunc func;
void* arg;
void* ret;
};
-static DWORD WINAPI Proxy(LPVOID arg)
+static unsigned WINAPI Proxy(LPVOID arg)
{
OTTDThread* t = (OTTDThread*)arg;
t->ret = t->func(t->arg);
@@ -122,13 +282,12 @@
OTTDThread* OTTDCreateThread(OTTDThreadFunc function, void* arg)
{
OTTDThread* t = MallocT<OTTDThread>(1);
- DWORD dwThreadId;
if (t == NULL) return NULL;
t->func = function;
t->arg = arg;
- t->thread = CreateThread(NULL, 0, Proxy, t, 0, &dwThreadId);
+ t->thread = (HANDLE)_beginthreadex(NULL, 0, Proxy, t, 0, &t->thread_id);
if (t->thread != NULL) {
return t;
@@ -153,6 +312,168 @@
void OTTDExitThread()
{
- ExitThread(0);
+ _endthreadex(0);
}
+
+/**
+ * C++ thread wrapper interface
+ */
+struct ThreadObject_Win32 : public ThreadObject {
+ uint m_id_thr;
+ HANDLE m_h_thr;
+ void (CDECL *m_proc)(void*);
+ void *m_param;
+ bool m_attached;
+
+ ThreadObject_Win32()
+ : m_id_thr(0)
+ , m_h_thr(NULL)
+ , m_proc(NULL)
+ , m_param(NULL)
+ , m_attached(false)
+ {}
+
+ /** Virtual destructor to allow 'delete' operator to work properly. */
+ /*virtual*/ ~ThreadObject_Win32()
+ {
+ if (m_h_thr != NULL) {
+ assert(m_attached || !IsRunning());
+ CloseHandle(m_h_thr);
+ m_h_thr = NULL;
+ }
+ }
+
+ /** Returns true if thread is running. */
+ /*virtual*/ bool IsRunning()
+ {
+ if (m_h_thr == NULL) return false;
+ DWORD exit_code = 0;
+ if (!GetExitCodeThread(m_h_thr, &exit_code)) return false;
+ return (exit_code == STILL_ACTIVE);
+ }
+
+ /** Waits for the thread exit. Returns true if thread exited. */
+ virtual bool WaitForStop()
+ {
+ assert(!IsCurrent());
+ if (!IsRunning()) return true;
+ DWORD res = WaitForSingleObject(m_h_thr, INFINITE);
+ return res == WAIT_OBJECT_0;
+ }
+
+ /** Returns true if this Thread Object belongs to the current (calling) thread. */
+ /*virtual*/ bool IsCurrent()
+ {
+ DWORD id_cur = GetCurrentThreadId();
+ return id_cur == m_id_thr;
+ }
+
+ /** Returns thread id of the thread associated to this Thread Object. */
+ /*virtual*/ uint Id()
+ {
+ return m_id_thr;
+ }
+
+ /** Begin thread execution by calling given procedure and passing given parameter to it. */
+ /*virtual*/ bool Begin(void (CDECL *proc)(void*), void *param)
+ {
+ assert(m_attached || !IsRunning());
+ m_attached = false;
+ m_proc = proc;
+ m_param = param;
+ m_h_thr = (HANDLE)_beginthreadex(NULL, 0, &stThreadProc, this, CREATE_SUSPENDED, &m_id_thr);
+ if (m_h_thr == NULL) return false;
+ ResumeThread(m_h_thr);
+ return true;
+ }
+
+ /** Thread procedure that is called by new thread. */
+ static uint CALLBACK stThreadProc(void *thr)
+ {
+ return ((ThreadObject_Win32*)thr)->ThreadProc();
+ }
+
+ /** Thread method that is called by new thread. */
+ uint ThreadProc()
+ {
+ uint id_thr = m_id_thr;
+ try {
+ m_proc(m_param);
+ } catch (...) {
+ DEBUG(misc, 0, "Exception in thread %d!", id_thr);
+ }
+ return 0;
+ }
+
+ /** Associates the current (caller) thread with this Thread Object. */
+ bool AttachCurrent()
+ {
+ assert(m_h_thr == NULL);
+ BOOL ret = DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &m_h_thr, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ if (!ret) return false;
+ m_id_thr = GetCurrentThreadId();
+ m_attached = true;
+ return true;
+ }
+};
+
+/** Used to construct new instance of thread object. */
+/*static*/ ThreadObject* ThreadObject::New()
+{
+ ThreadObject_Win32 *thr = new ThreadObject_Win32;
+ return thr;
+}
+
+/** Returns thread object instance that belongs to the current (calling) thread. */
+/*static*/ ThreadObject* ThreadObject::Current()
+{
+ ThreadObject_Win32 *thr = new ThreadObject_Win32;
+ if (!thr->AttachCurrent()) {
+ /* attach failed */
+ delete thr;
+ return NULL;
+ }
+ return thr;
+}
+
+/** Returns thread id of the current (calling) thread. */
+/*static*/ uint ThreadObject::CurrentId()
+{
+ return GetCurrentThreadId();
+}
+
+
+
+/**
+ * Event object that is signaled manually by Set() and reset automatically when thread passes Wait().
+ */
+struct AutoResetEvent_Win32 : public AutoResetEvent {
+ HANDLE m_handle;
+
+ AutoResetEvent_Win32()
+ {
+ m_handle = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+ }
+
+ virtual ~AutoResetEvent_Win32()
+ {
+ ::CloseHandle(m_handle);
+ }
+
+ virtual void Set()
+ {
+ ::SetEvent(m_handle);
+ }
+
+ virtual void Wait()
+ {
+ ::WaitForSingleObject(m_handle, INFINITE);
+ }
+};
+
+/*static*/ AutoResetEvent* AutoResetEvent::New()
+{
+ return new AutoResetEvent_Win32();
+}
+
#endif
--- a/src/thread.h Thu Mar 22 23:12:05 2007 +0000
+++ b/src/thread.h Fri Mar 23 21:22:28 2007 +0000
@@ -11,4 +11,44 @@
void* OTTDJoinThread(OTTDThread*);
void OTTDExitThread();
+
+/**
+ * C++ thread wrapper interface
+ */
+struct ThreadObject {
+ /** Virtual destructor to allow 'delete' operator to work properly. */
+ virtual ~ThreadObject() {};
+ /** Returns true if thread is running. */
+ virtual bool IsRunning() = 0;
+ /** Waits for the thread exit. Returns true if thread exited. */
+ virtual bool WaitForStop() = 0;
+ /** Returns true if this Thread Object belongs to the current (calling) thread. */
+ virtual bool IsCurrent() = 0;
+ /** Returns thread id of the thread associated to this Thread Object. */
+ virtual uint Id() = 0;
+ /** Begin thread execution by calling given procedure and passing given parameter to it. */
+ virtual bool Begin(void (CDECL *proc)(void*), void *param) = 0;
+ /** Used to construct new instance of thread object. */
+ static ThreadObject* New();
+ /** Returns thread object instance that belongs to the current (calling) thread. */
+ static ThreadObject* Current();
+ /** Returns thread id of the current (calling) thread. */
+ static uint CurrentId();
+};
+
+/**
+ * Event object that is signaled manually by Set() and reset automatically when thread passes Wait().
+ */
+struct AutoResetEvent {
+ /** Virtual destructor to allow 'delete' operator to work properly. */
+ virtual ~AutoResetEvent() {};
+ /** Set the event state to signaled so that one thread can pass the Wait() method. */
+ virtual void Set() = 0;
+ /** Wait until event is signaled by calling Set() */
+ virtual void Wait() = 0;
+ /** Used to construct new instance of event object. */
+ static AutoResetEvent* New();
+};
+
+
#endif /* THREAD_H */