src/ai/ai_threads.cpp
branchnoai
changeset 9444 fd27df7ca2a0
parent 9441 03da911c8d5f
child 9445 2a4a87b968e2
--- 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;
-}