(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.
--- 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);