--- a/src/ai/ai_threads.cpp Fri Mar 16 21:35:01 2007 +0000
+++ b/src/ai/ai_threads.cpp Fri Mar 16 22:00:07 2007 +0000
@@ -9,91 +9,242 @@
#include "../debug.h"
#include "../thread.h"
#include "ai.h"
+#include "ai_threads.h"
#include "api/ai_controller.hpp"
-static int _ai_suspended[MAX_PLAYERS];
-static bool _ai_last_command[MAX_PLAYERS];
-static bool _ai_allow_running[MAX_PLAYERS];
-static bool _ai_quit_thread[MAX_PLAYERS];
-static OTTDThread *_ai_thread[MAX_PLAYERS];
+#include <pthread.h>
/**
- * Resume the AI handler of a player.
+ * Function that is started as process of the new thread.
+ * @param arg some random argument, in this case a AIThreadState
*/
-static void AI_ResumePlayer(PlayerID player)
+static void *AI_ThreadRun(void *arg);
+
+/** The overall state of an AI threading wise */
+class AIThreadState {
+ /** 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
+ };
+
+ 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
+
+ PlayerID player_id; ///< The ID of the current player
+ 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) {}
+
+ /**
+ * 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.
+ */
+ void Initialise(PlayerID player, AIController *controller)
+ {
+ assert(this->state == STOPPED);
+ this->thread = NULL;
+ this->state = INITIALIZED;
+
+ pthread_mutex_init(&this->mutex, NULL);
+ pthread_cond_init(&this->ai_cont, NULL);
+ pthread_cond_init(&this->main_cont, NULL);
+
+ this->player_id = player;
+ this->ticks_to_sleep = 0;
+ this->last_command_res = false;
+ this->controller = controller;
+ }
+
+ /**
+ * 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.
+ */
+ void Start()
+ {
+ assert(this->state == INITIALIZED);
+ pthread_mutex_lock(&this->mutex);
+
+ this->state = STARTING;
+ this->thread = OTTDCreateThread(AI_ThreadRun, 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);
+ }
+
+ /**
+ * The method that runs in the new Thread.
+ * @note should be called by the newly spawned thread in Start.
+ */
+ void ThreadRun()
+ {
+ assert(this->state == STARTING);
+
+ 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);
+ /* The AI stopped on it's own */
+ this->state = STOPPED;
+ AI_PlayerDied(this->player_id);
+ } else {
+ this->state = STOPPED;
+ }
+
+ pthread_cond_signal(&this->main_cont);
+ pthread_mutex_unlock(&this->mutex);
+ }
+
+ /**
+ * Suspend this AI.
+ * @param timeout time to suspend. < 0 means infinite (MultiPlayer only!)
+ * @note should be called from the AI thread.
+ */
+ void Suspend(int timeout)
+ {
+ assert(this->state == RUNNING);
+
+ 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;
+ }
+
+ /**
+ * Set the AI thread to resume at the next call of RunTick.
+ * @note should NOT be called from the AI thread.
+ */
+ void Resume()
+ {
+ assert(this->state == SUSPENDED);
+ this->ticks_to_sleep = 0;
+ }
+
+ /**
+ * Let the AI thread run for a while and return when it is done.
+ * However, when the thread is suspened 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.
+ */
+ void RunTick()
+ {
+ assert(this->state == SUSPENDED);
+ this->controller->IncreaseTick();
+
+ 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);
+ }
+
+ /**
+ * 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);
+
+ this->state = STOPPING;
+ this->controller->Stop();
+
+ /* Resurect the AI thread */
+ pthread_mutex_lock(&this->mutex);
+ pthread_cond_signal(&this->ai_cont);
+ pthread_mutex_unlock(&this->mutex);
+
+ /* Wait till the AI thread has finished */
+ OTTDJoinThread(this->thread);
+ this->state = STOPPED;
+ }
+};
+
+/**
+ * All AI Thread states.
+ */
+static AIThreadState _ai_state[MAX_PLAYERS];
+
+
+static void *AI_ThreadRun(void *arg)
{
- DEBUG(ai, 6, "[Threads] Resume request for player '%d' at %d", _current_player, _tick_counter);
- _ai_suspended[player] = 0;
+ ((AIThreadState*)arg)->ThreadRun();
+ return NULL;
}
/**
* Suspend the AI handler of a player.
- * @param time Time to suspend. -1 means infinite (MultiPlayer only!)
+ * @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 time)
+void AI_SuspendPlayer(PlayerID player, int timeout)
{
- DEBUG(ai, 6, "[Threads] Suspend request for player '%d' at %d", player, _tick_counter);
- _ai_suspended[player] = time;
- /* Now hold this thread till the number reaches 0 again */
- while ((_ai_suspended[player] != 0 || _ai_allow_running[player] == false) && _ai_quit_thread[_current_player] == false) CSleep(1);
- if (_ai_quit_thread[player]) OTTDExitThread();
+ _ai_state[player].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_RunPlayer(PlayerID player, AIController *controller)
+void AI_RunTick(PlayerID player)
{
- /* Now suspend the main thread till the AI is being suspeanded */
- _ai_allow_running[player] = true;
- while (_ai_suspended[player] == 0 && _ai_quit_thread[player] == false) CSleep(1);
- _ai_allow_running[player] = false;
-}
-
-static void *AI_ThreadRun(void *arg)
-{
- AIController *controller = (AIController *)arg;
- controller->Start();
-
- /* Mark the thread as dead to avoid deadlocks */
- _ai_quit_thread[_current_player] = true;
-
- return NULL;
+ _ai_state[player].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)
{
- /* Launch up a thread */
- _ai_quit_thread[player] = false;
- _ai_thread[player] = OTTDCreateThread(AI_ThreadRun, controller);
- /* XXX -- We should handle this more nicely */
- assert(_ai_thread[player] != NULL);
- /* Now suspend the main thread till the AI is being suspended */
- _ai_allow_running[player] = true;
- while (_ai_suspended[player] == 0 && _ai_quit_thread[player] == false) CSleep(1);
- _ai_allow_running[player] = false;
-}
-
-void AI_RemovePlayer(PlayerID player)
-{
- _ai_quit_thread[player] = true;
- AI_ResumePlayer(player);
- _ai_allow_running[player] = true;
- OTTDJoinThread(_ai_thread[player]);
- _ai_allow_running[player] = false;
+ _ai_state[player].Initialise(player, controller);
+ _ai_state[player].Start();
}
/**
- * Returns the result of the last command which was received by the
- * MultiPlayer Command Callback.
+ * Stops the given player
+ * @param player the (AI) player to stop
*/
-bool AI_GetCallbackResult(PlayerID player)
+void AI_StopPlayer(PlayerID player)
{
- return _ai_last_command[player];
+ _ai_state[player].Stop();
}
/**
@@ -102,27 +253,18 @@
*/
void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2)
{
- /* Store if we were a success or not */
- _ai_last_command[_current_player] = success;
- /* Resume the player */
- AI_ResumePlayer(_current_player);
+ /* Store if we were a success or not and resume */
+ _ai_state[_current_player].last_command_res = success;
+ _ai_state[_current_player].Resume();
}
/**
- * Checks the state of a suspended player and resumes it if needed.
+ * 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
*/
-void AI_CheckSuspendState(PlayerID player)
+bool AI_GetCallbackResult(PlayerID player)
{
- if (_ai_suspended[player] <= 0) return;
-
- if (_ai_suspended[player] == 1) AI_ResumePlayer(player);
- else _ai_suspended[player]--;
+ return _ai_state[player].last_command_res;
}
-
-/**
- * Checks if a player is suspended.
- */
-bool AI_IsSuspended(PlayerID player)
-{
- return _ai_suspended[player] != 0;
-}