# HG changeset patch # User truelight # Date 1174064629 0 # Node ID 03da911c8d5f4da79c732c4503d14b20a6a3c2b8 # Parent 0986434f3af880fa167faf0b521e7bced51791e6 (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. diff -r 0986434f3af8 -r 03da911c8d5f bin/ai/SQNoAI/main.nut --- 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"; } diff -r 0986434f3af8 -r 03da911c8d5f projects/openttd.vcproj --- 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 @@ + + + + + + + + + + + + + + + + + + + + diff -r 0986434f3af8 -r 03da911c8d5f source.list --- 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 diff -r 0986434f3af8 -r 03da911c8d5f src/ai/NoAI/NoAI.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)); + } + } +} diff -r 0986434f3af8 -r 03da911c8d5f src/ai/NoAI/NoAI.hpp --- /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 { +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 */ diff -r 0986434f3af8 -r 03da911c8d5f src/ai/ai.cpp --- 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]; } diff -r 0986434f3af8 -r 03da911c8d5f src/ai/ai.h --- 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 */ diff -r 0986434f3af8 -r 03da911c8d5f src/ai/ai_squirrel.cpp --- 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"); } diff -r 0986434f3af8 -r 03da911c8d5f src/ai/ai_squirrel.hpp --- 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 { diff -r 0986434f3af8 -r 03da911c8d5f src/ai/ai_threads.cpp --- /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; +} diff -r 0986434f3af8 -r 03da911c8d5f src/ai/api/ai_company.cpp --- 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" diff -r 0986434f3af8 -r 03da911c8d5f src/ai/api/ai_controller.cpp --- /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); +} diff -r 0986434f3af8 -r 03da911c8d5f src/ai/api/ai_controller.hpp --- 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 SQAIController("AIController"); SQAIController.PreRegister(engine); SQAIController.DefSQFunction(engine, &AIControllerSquirrel::GetTick, "GetTick"); + SQAIController.DefSQFunction(engine, &AIControllerSquirrel::Sleep, "Sleep"); SQAIController.PostRegister(engine); } #endif /* SQUIRREL_CLASS */ diff -r 0986434f3af8 -r 03da911c8d5f src/ai/api/ai_object.cpp --- 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; diff -r 0986434f3af8 -r 03da911c8d5f src/ai/api/ai_object.hpp --- 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. diff -r 0986434f3af8 -r 03da911c8d5f src/callback_table.cpp --- 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);