# HG changeset patch # User KUDr # Date 1174684948 0 # Node ID e31710af1ca00155e344c8f78ee4a0e9d663fa2e # Parent 258f78c74b0c10968365260de71543773ea0dcb0 (svn r9419) [NoAI] -Codechange: support AI threads also on Win32 (using threads on Win95 and fibers on other Win32 platforms) diff -r 258f78c74b0c -r e31710af1ca0 projects/openttd.vcproj --- 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"> + + + + diff -r 258f78c74b0c -r e31710af1ca0 source.list --- 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 diff -r 258f78c74b0c -r e31710af1ca0 src/ai/ai_threads.cpp --- 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 +#include "../misc/autoptr.hpp" +#include "../misc/countedptr.hpp" +#include -/** 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 > 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 evt_go; ///< Win32 event handle that signals the thread can go + ThreadState next_state; ///< Next state after wakeup + AutoPtrT 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 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 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 +#include + +/** + * 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 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; } diff -r 258f78c74b0c -r e31710af1ca0 src/misc/countedobj.cpp --- /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; +} + diff -r 258f78c74b0c -r e31710af1ca0 src/misc/countedptr.hpp --- 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 >) + */ +template 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. + * + * @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 */ diff -r 258f78c74b0c -r e31710af1ca0 src/thread.cpp --- 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 #include "helpers.hpp" @@ -66,6 +67,8 @@ #elif defined(UNIX) #include +#include +#include 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 +#include 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(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 diff -r 258f78c74b0c -r e31710af1ca0 src/thread.h --- 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 */