(svn r9419) [NoAI] -Codechange: support AI threads also on Win32 (using threads on Win95 and fibers on other Win32 platforms)
/* $Id$ */
/** @file ai_threads.cpp handles the threading of AIs */
#include "../stdafx.h"
#include "../openttd.h"
#include "../variables.h"
#include "../player.h"
#include "../debug.h"
#include "../thread.h"
#include "ai.h"
#include "ai_threads.h"
#include "api/ai_controller.hpp"
#include "api/ai_object.hpp"
#include "../misc/autoptr.hpp"
#include "../misc/countedptr.hpp"
#include <map>
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
RUNNING, ///< We are inside the main AI
SUSPENDED, ///< We are suspended (either by sleep or block)
STOPPING, ///< We are being stopped
STOPPED, ///< The main AI has stopped
};
static const int MAIN_THREAD = -1;
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 command
/**
* 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).
*/
protected:
AIThread(PlayerID player, AIController *controller)
{
/* register new thread */
s_threads[player] = this;
/* ensure that main thread has its wrapper too */
if (player != MAIN_THREAD) stFind(MAIN_THREAD);
this->state = INITIALIZED;
this->fiber_id = player;
this->ticks_to_sleep = 0;
this->controller = controller;
this->last_command_res = false;
}
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);
}
/**
* Start the AI thread and return once the AI calls Sleep or
* any other suspending command.
* @note should be called after Initialize, but before any other function.
*/
public:
virtual bool Start() = 0;
/**
* The method that runs in the new Thread.
* @note should be called by the newly spawned thread in Start.
*/
protected:
void ThreadProc()
{
assert(this->state == STARTING);
this->state = RUNNING;
this->controller->Start();
if (this->state != STOPPING) {
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((PlayerID)this->fiber_id);
} else {
this->state = STOPPED;
}
}
/**
* Suspend this AI.
* @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->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->ticks_to_sleep = timeout;
/* 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 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 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
* of the MP. Therefor we keep adding value to ticks_to_sleep till it
* reaches -1, and we 'hang' it there infinitely, until the MP commands
* comes in. In the case it happens sooner, the Resume() codes handles it
* nicely and makes the value positive with the remaining ticks to wait. */
if (this->ticks_to_sleep < -1) this->ticks_to_sleep++;
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->SwitchToThis(RUNNING);
}
/**
* Stop the AI thread and wait till it is stopped.
* @note should NOT be called from the AI thread.
*/
public:
virtual void Stop() = 0;
/**
* Called before the AI thread finishes. Should wakeup the main thread.
*/
public:
virtual void OnStop() {};
/**
* 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;
/**
* 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();
}
};
/**
* 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 */ 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*/
/**
* Suspend the AI handler of a player.
* @param player the player to suspend for
* @param timeout Time to suspend. < 0 means infinite (MultiPlayer only!)
* @note This should be called from within the thread running this player!
*/
void AI_SuspendPlayer(PlayerID player, int 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);
}
/**
* Run the thread of an AI player. Return when it is suspended again.
* @param player the player to run this tick for
*/
void AI_RunTick(PlayerID player)
{
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();
}
/**
* Run an AI player for the first time.
* @param player the (AI) player to start
* @param controller the actual AI
*/
void AI_StartPlayer(PlayerID player, AIController *controller)
{
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();
}
/**
* Stops the given player
* @param player the (AI) player to stop
*/
void AI_StopPlayer(PlayerID player)
{
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);
}
/**
* Callback for when a network command was executed.
* As commands are always ordered, we don't have to worry about right command.
*/
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 */
thr->last_command_res = success;
/* Store some values inside the AIObject static memory */
AIObject::SetNewVehicleID(_new_vehicle_id);
thr->Resume();
}
/**
* Returns the result of the last command which was received by the
* MultiPlayer Command Callback.
* @param player the player to the callback information about
* @return true if and only if the last sent command succeeded
*/
bool AI_GetCallbackResult(PlayerID player)
{
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;
}