(svn r9255) [NoAI] -Add: each AI now runs in a seperate thread. The main thread is suspended if any AI thread is running, only one AI thread runs at the time. noai
authortruelight
Fri, 16 Mar 2007 17:03:49 +0000
branchnoai
changeset 9441 03da911c8d5f
parent 9440 0986434f3af8
child 9442 1b606e5fac13
(svn r9255) [NoAI] -Add: each AI now runs in a seperate thread. The main thread is suspended if any AI thread is running, only one AI thread runs at the time.
-Note: the threading is pretty poor right now, with slow CSleep(1) in them. Code is required to make this better, but it is a start.
-Change: entry point in AI is now Start(). If you return from that, your AI dies.
-Change: if you now run a DoCommand, you won't get a result till one tick later, or worse in MP.
bin/ai/SQNoAI/main.nut
projects/openttd.vcproj
projects/openttd_vs80.vcproj
source.list
src/ai/NoAI/NoAI.cpp
src/ai/NoAI/NoAI.hpp
src/ai/ai.cpp
src/ai/ai.h
src/ai/ai_squirrel.cpp
src/ai/ai_squirrel.hpp
src/ai/ai_threads.cpp
src/ai/api/ai_company.cpp
src/ai/api/ai_controller.cpp
src/ai/api/ai_controller.hpp
src/ai/api/ai_object.cpp
src/ai/api/ai_object.hpp
src/callback_table.cpp
--- a/bin/ai/SQNoAI/main.nut	Fri Mar 16 10:14:14 2007 +0000
+++ b/bin/ai/SQNoAI/main.nut	Fri Mar 16 17:03:49 2007 +0000
@@ -16,12 +16,17 @@
 		this.town = AITown();
 	}
 
-	function GameLoop();
+	function Start();
 }
 
 /* Define the GameLoop. This is called every tick. */
-function SQNoAI::GameLoop()
+function SQNoAI::Start()
 {
+	while (true) {
+		print("Look at me!" + this.GetTick());
+		this.Sleep(5);
+	}
+	return;
 	if (this.GetTick() == 1) {
 		if (!this.company.SetCompanyName("SQNoAI")) {
 			this.company.SetCompanyName("SQNoAI " + this.base.Random());
@@ -71,6 +76,7 @@
 	}
 }
 
+
 class FSQNoAI extends AIFactory {
 	function GetAuthor()      { return "OpenTTD Dev Team"; }
 	function GetName()        { return "SQNoAI"; }
--- a/projects/openttd.vcproj	Fri Mar 16 10:14:14 2007 +0000
+++ b/projects/openttd.vcproj	Fri Mar 16 17:03:49 2007 +0000
@@ -988,6 +988,19 @@
 			<File
 				RelativePath=".\..\src\ai\ai_squirrel.hpp">
 			</File>
+			<File
+				RelativePath=".\..\src\ai\ai_threads.cpp">
+			</File>
+		</Filter>
+		<Filter
+			Name="AI C++"
+			Filter="">
+			<File
+				RelativePath=".\..\src\ai\NoAI\NoAI.cpp">
+			</File>
+			<File
+				RelativePath=".\..\src\ai\NoAI\NoAI.hpp">
+			</File>
 		</Filter>
 		<Filter
 			Name="AI API"
@@ -1030,6 +1043,9 @@
 				RelativePath=".\..\src\ai\api\ai_company.cpp">
 			</File>
 			<File
+				RelativePath=".\..\src\ai\api\ai_controller.cpp">
+			</File>
+			<File
 				RelativePath=".\..\src\ai\api\ai_industry.cpp">
 			</File>
 			<File
--- a/projects/openttd_vs80.vcproj	Fri Mar 16 10:14:14 2007 +0000
+++ b/projects/openttd_vs80.vcproj	Fri Mar 16 17:03:49 2007 +0000
@@ -1539,6 +1539,22 @@
 				RelativePath=".\..\src\ai\ai_squirrel.hpp"
 				>
 			</File>
+			<File
+				RelativePath=".\..\src\ai\ai_threads.cpp"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="AI C++"
+			>
+			<File
+				RelativePath=".\..\src\ai\NoAI\NoAI.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\..\src\ai\NoAI\NoAI.hpp"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="AI API"
@@ -1592,6 +1608,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\ai\api\ai_controller.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\ai\api\ai_industry.cpp"
 				>
 			</File>
--- a/source.list	Fri Mar 16 10:14:14 2007 +0000
+++ b/source.list	Fri Mar 16 17:03:49 2007 +0000
@@ -300,6 +300,11 @@
 ai/ai_factory.hpp
 ai/ai_squirrel.cpp
 ai/ai_squirrel.hpp
+ai/ai_threads.cpp
+
+# AI C++
+ai/NoAI/NoAI.cpp
+ai/NoAI/NoAI.hpp
 
 # AI API
 ai/api/ai_base.hpp
@@ -315,6 +320,7 @@
 ai/api/ai_base.cpp
 ai/api/ai_cargo.cpp
 ai/api/ai_company.cpp
+ai/api/ai_controller.cpp
 ai/api/ai_industry.cpp
 ai/api/ai_map.cpp
 ai/api/ai_object.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/NoAI/NoAI.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -0,0 +1,68 @@
+/* $Id$ */
+
+/** @file NoAI.cpp a simple C++ AI that will never be an AI */
+
+#include "NoAI.hpp"
+
+static FNoAI iFNoAI; ///< Tell the AI-core that we have an AI with which we like to play.
+
+/* virtual */ void NoAI::Start()
+{
+	while (true) {
+		printf("Start NoAI %d\n", this->GetTick());
+		this->Sleep(5);
+	}
+	return;
+	if (this->GetTick() == 1) {
+		if (!this->company.SetCompanyName("NoAI")) {
+			char name[32];
+			snprintf(name, lengthof(name), "NoAI %d", this->base.Rand());
+			this->company.SetCompanyName(name);
+		}
+		printf("Map size: %d by %d, %d tiles\n", this->map.GetMapSizeX(),
+				this->map.GetMapSizeY(), this->map.GetMapSize());
+	}
+
+	if (this->GetTick() < 10) {
+		char *cargo_label = this->cargo.GetCargoLabel(this->GetTick());
+		printf("%s is %sfreight and the income is %d per 20 tiles in 10 days\n",
+				cargo_label, this->cargo.IsFreight(this->GetTick()) ? "" : "not ",
+				this->cargo.GetCargoIncome(20, 10, this->GetTick()));
+		free(cargo_label);
+	}
+
+	if (this->GetTick() < this->industry.GetMaxIndustryID()) {
+		if (this->industry.IsValidIndustry(this->GetTick())) {
+			char *industry_name = this->industry.GetName(this->GetTick());
+			TileIndex t = this->industry.GetLocation(this->GetTick());
+
+			printf("Industry %s [%d of %d] at (%d, %d)\n",
+					industry_name, this->GetTick(), this->industry.GetIndustryCount(),
+					this->map.GetTileX(t), this->map.GetTileY(t));
+		}
+	}
+
+	if (this->GetTick() % 10 == 0) {
+		char *company_name = this->company.GetCompanyName();
+		printf("%s has loaned %d\n", company_name, this->company.GetLoanAmount());
+		free(company_name);
+	}
+
+	if (this->GetTick() % 14 == 0) {
+		uint level = (this->company.GetMaxLoanAmount() / this->company.GetLoanInterval()) + 1;
+		this->company.SetLoanAmount(this->base.RandRange(level) * this->company.GetLoanInterval());
+	}
+
+	if (this->GetTick() % 13 == 0) {
+		TownID town_id = this->base.RandRange(this->town.GetMaxTownID());
+		if (this->town.IsValidTown(town_id)) {
+			char *town_name = this->town.GetName(town_id);
+			TileIndex t = this->town.GetLocation(town_id);
+
+			printf("Town %s [%d of %d] at (%d, %d) with %d inhabitants\n",
+					town_name, town_id, this->town.GetTownCount(),
+					this->map.GetTileX(t), this->map.GetTileY(t),
+					this->town.GetPopulation(town_id));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/NoAI/NoAI.hpp	Fri Mar 16 17:03:49 2007 +0000
@@ -0,0 +1,40 @@
+/* $Id$ */
+
+/** @file NoAI.hpp declarations of the class for a simple C++ AI that will never be an AI */
+
+#ifndef NOAI_HPP
+#define NOAI_HPP
+
+#include "../ai_factory.hpp"
+#include "../api/ai_controller.hpp"
+#include "../api/ai_base.hpp"
+#include "../api/ai_cargo.hpp"
+#include "../api/ai_company.hpp"
+#include "../api/ai_industry.hpp"
+#include "../api/ai_map.hpp"
+#include "../api/ai_town.hpp"
+
+class NoAI: public AIController {
+private:
+	AIBase base;
+	AICargo cargo;
+	AICompany company;
+	AIIndustry industry;
+	AIMap map;
+	AITown town;
+
+public:
+	/* virtual */ void Start();
+};
+
+class FNoAI: public AIFactory<FNoAI> {
+public:
+	/* virtual */ const char   *GetAuthor()      { return "OpenTTD Dev Team"; }
+	/* virtual */ const char   *GetName()        { return "NoAI"; }
+	/* virtual */ const char   *GetDescription() { return "Rather simple AI that tests all the functions of the AI layer."; }
+	/* virtual */ int           GetVersion()     { return 1; }
+	/* virtual */ const char   *GetDate()        { return "2007-03-14"; }
+	/* virtual */ AIController *CreateInstance() { return new NoAI(); }
+};
+
+#endif /* NOAI_HPP */
--- a/src/ai/ai.cpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/ai.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -27,9 +27,12 @@
 
 	/* If this is NULL, it means we couldn't find an AI for this player */
 	if (_ai_player[player] == NULL) return;
+	AI_CheckSuspendState(player);
 
 	_ai_player[player]->IncreaseTick();
-	_ai_player[player]->GameLoop();
+
+	if (AI_IsSuspended(player)) return;
+	AI_RunPlayer(player, _ai_player[player]);
 }
 
 
@@ -81,7 +84,10 @@
 	if (_ai_player[player] == NULL) {
 		DEBUG(ai, 0, "Couldn't find a suitable AI to start for company '%d'", player);
 		/* XXX -- We have no clean way to handle this yet! */
+		return;
 	}
+	_current_player = player;
+	AI_StartPlayer(player, _ai_player[player]);
 }
 
 /**
@@ -91,6 +97,7 @@
 {
 	if (!_ai_enabled) return;
 
+	AI_RemovePlayer(player);
 	/* Called if this AI died */
 	delete _ai_player[player];
 }
--- a/src/ai/ai.h	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/ai.h	Fri Mar 16 17:03:49 2007 +0000
@@ -5,6 +5,8 @@
 #ifndef AI_H
 #define AI_H
 
+class AIController;
+
 // ai.c
 void AI_StartNewAI(PlayerID player);
 void AI_PlayerDied(PlayerID player);
@@ -13,4 +15,14 @@
 void AI_Uninitialize();
 bool AI_AllowNewAI();
 
+// ai_threads.cpp
+void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2);
+bool AI_GetCallbackResult(PlayerID player);
+void AI_SuspendPlayer(PlayerID player, int time);
+void AI_CheckSuspendState(PlayerID player);
+bool AI_IsSuspended(PlayerID player);
+void AI_RunPlayer(PlayerID player, AIController *controller);
+void AI_StartPlayer(PlayerID player, AIController *controller);
+void AI_RemovePlayer(PlayerID player);
+
 #endif /* AI_H */
--- a/src/ai/ai_squirrel.cpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/ai_squirrel.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -205,7 +205,7 @@
 	delete this->engine;
 }
 
-/* virtual */ void AIControllerSquirrel::GameLoop()
+/* virtual */ void AIControllerSquirrel::Start()
 {
-	this->engine->CallMethod(this->SQ_instance, "GameLoop");
+	this->engine->CallMethod(this->SQ_instance, "Start");
 }
--- a/src/ai/ai_squirrel.hpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/ai_squirrel.hpp	Fri Mar 16 17:03:49 2007 +0000
@@ -19,9 +19,10 @@
 	AIControllerSquirrel(const char *script_dir, const char *class_name);
 	~AIControllerSquirrel();
 
-	/* virtual */ void GameLoop();
+	/* virtual */ void Start();
 
 	uint GetTick() { return AIController::GetTick(); }
+	void Sleep(uint ticks) { return AIController::Sleep(ticks); }
 };
 
 class FSquirrel: public AIFactory<FSquirrel> {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/ai_threads.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -0,0 +1,128 @@
+/* $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 "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];
+
+/**
+ * Resume the AI handler of a player.
+ */
+static void AI_ResumePlayer(PlayerID player)
+{
+	DEBUG(ai, 6, "[Threads] Resume request for player '%d' at %d", _current_player, _tick_counter);
+	_ai_suspended[player] = 0;
+}
+
+/**
+ * Suspend the AI handler of a player.
+ * @param time Time to suspend. -1 means infinite (MultiPlayer only!)
+ * @note This should be called from within the thread running this player!
+ */
+void AI_SuspendPlayer(PlayerID player, int time)
+{
+	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();
+}
+
+/**
+ * Run the thread of an AI player. Return when it is suspended again.
+ */
+void AI_RunPlayer(PlayerID player, AIController *controller)
+{
+	/* 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;
+}
+
+/**
+ * Run an AI player for the first time.
+ */
+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;
+}
+
+/**
+ * Returns the result of the last command which was received by the
+ *  MultiPlayer Command Callback.
+ */
+bool AI_GetCallbackResult(PlayerID player)
+{
+	return _ai_last_command[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)
+{
+	/* Store if we were a success or not */
+	_ai_last_command[_current_player] = success;
+	/* Resume the player */
+	AI_ResumePlayer(_current_player);
+}
+
+/**
+ * Checks the state of a suspended player and resumes it if needed.
+ */
+void AI_CheckSuspendState(PlayerID player)
+{
+	if (_ai_suspended[player] <= 0) return;
+
+	if (_ai_suspended[player] == 1) AI_ResumePlayer(player);
+	else _ai_suspended[player]--;
+}
+
+/**
+ * Checks if a player is suspended.
+ */
+bool AI_IsSuspended(PlayerID player)
+{
+	return _ai_suspended[player] != 0;
+}
--- a/src/ai/api/ai_company.cpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/api/ai_company.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -3,6 +3,7 @@
 /** @file ai_company.cpp handles the functions of the AICompany class */
 
 #include "ai_company.hpp"
+#include "../../command.h"
 #include "../../player.h"
 #include "../../economy.h"
 #include "../../strings.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/api/ai_controller.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -0,0 +1,14 @@
+/* $Id$ */
+
+/** @file ai_controller.cpp handles the functions of the AIControler class */
+
+#include "ai_controller.hpp"
+#include "../../stdafx.h"
+#include "../../openttd.h"
+#include "../../player.h"
+#include "../ai.h"
+
+void AIController::Sleep(uint ticks)
+{
+	AI_SuspendPlayer(_current_player, ticks);
+}
--- a/src/ai/api/ai_controller.hpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/api/ai_controller.hpp	Fri Mar 16 17:03:49 2007 +0000
@@ -19,9 +19,9 @@
 	virtual ~AIController() { }
 
 	/**
-	 * This function is called every tick.
+	 * This function is called once to start the AI.
 	 */
-	virtual void GameLoop() = 0;
+	virtual void Start() = 0;
 
 	/**
 	 * Increase the internal ticker.
@@ -32,6 +32,13 @@
 	 * Return the value of the ticker.
 	 */
 	uint GetTick() { return this->tick; }
+
+	/**
+	 * Sleep for X ticks. The code continues after this line when the X AI ticks
+	 *  are passed. Mind that an AI tick is different from in-game ticks and
+	 *  differ per AI speed.
+	 */
+	void Sleep(uint ticks);
 };
 
 #endif /* AI_CONTROLLER_HPP */
@@ -53,6 +60,7 @@
 	DefSQClass <AIControllerSquirrel> SQAIController("AIController");
 	SQAIController.PreRegister(engine);
 	SQAIController.DefSQFunction(engine, &AIControllerSquirrel::GetTick, "GetTick");
+	SQAIController.DefSQFunction(engine, &AIControllerSquirrel::Sleep, "Sleep");
 	SQAIController.PostRegister(engine);
 }
 #endif /* SQUIRREL_CLASS */
--- a/src/ai/api/ai_object.cpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/api/ai_object.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -3,7 +3,9 @@
 /** @file ai_object.cpp handles the commands-related functions of the AIObject class */
 
 #include "ai_object.hpp"
+#include "../../command.h"
 #include "../../player.h"
+#include "../ai.h"
 
 bool AIObject::CmdFailed(int32 res)
 {
@@ -15,7 +17,7 @@
 	return !::CmdFailed(res);
 }
 
-int32 AIObject::DoCommandCc(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc, CommandCallback *callback)
+int32 AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc)
 {
 	PlayerID old_lp;
 	int32 res = 0;
@@ -44,17 +46,21 @@
 		 * adjust it, and put it back right after the function */
 		old_lp = _local_player;
 		_local_player = _current_player;
+		::NetworkSend_Command(tile, p1, p2, procc, CcAI);
+		_local_player = old_lp;
 
-		::NetworkSend_Command(tile, p1, p2, procc, callback);
-
-		/* Set _local_player back */
-		_local_player = old_lp;
+		/* Suspend the AI till the command is really executed */
+		AI_SuspendPlayer(_current_player, -1);
+		/* Check if the callback still agrees with us, else return error */
+		if (!AI_GetCallbackResult(_current_player)) res = CMD_ERROR;
 	} else {
 #else
 	{
 #endif
 		/* For SinglePlayer we execute the command immediatly */
-		::DoCommandP(tile, p1, p2, callback, procc);
+		::DoCommandP(tile, p1, p2, NULL, procc);
+		/* Suspend the AI player for 1 tick, so it simulates MultiPlayer */
+		AI_SuspendPlayer(_current_player, 1);
 	}
 
 	return res;
--- a/src/ai/api/ai_object.hpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/ai/api/ai_object.hpp	Fri Mar 16 17:03:49 2007 +0000
@@ -5,21 +5,15 @@
 #ifndef AI_OBJECT_HPP
 #define AI_OBJECT_HPP
 
-#include "../../stdafx.h"    // Needed for functions.h
-#include "../../functions.h" // Needed for command.h
-#include "../../command.h"   // Needed for CommandCallback
+#include "../../stdafx.h"
+#include "../../functions.h"
 
 class AIObject {
 protected:
 	/**
-	 * Executes a raw DoCommandCc for the AI.
-	 */
-	int32 DoCommandCc(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc, CommandCallback *callback);
-
-	/**
 	 * Executes a raw DoCommand for the AI.
 	 */
-	int32 DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc) { return this->DoCommandCc(tile, p1, p2, flags, procc, NULL); }
+	int32 DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc);
 
 	/**
 	 * Checks if the result of a DoCommand went wrong.
--- a/src/callback_table.cpp	Fri Mar 16 10:14:14 2007 +0000
+++ b/src/callback_table.cpp	Fri Mar 16 17:03:49 2007 +0000
@@ -58,6 +58,9 @@
 CommandCallback CcBuildLoco;
 CommandCallback CcCloneTrain;
 
+/* ai/ai.cpp */
+CommandCallback CcAI;
+
 CommandCallback *_callback_table[] = {
 	/* 0x00 */ NULL,
 	/* 0x01 */ CcBuildAircraft,
@@ -84,7 +87,8 @@
 	/* 0x16 */ CcCloneRoadVeh,
 	/* 0x17 */ CcCloneShip,
 	/* 0x18 */ CcCloneTrain,
-	/* 0x19 */ CcCloneVehicle
+	/* 0x19 */ CcAI,
+	/* 0x1A */ CcCloneVehicle
 };
 
 const int _callback_table_count = lengthof(_callback_table);