truelight@9441: /* $Id$ */ truelight@9441: truelight@9441: /** @file ai_threads.cpp handles the threading of AIs */ truelight@9441: truelight@9441: #include "../stdafx.h" truelight@9441: #include "../openttd.h" truelight@9441: #include "../variables.h" truelight@9441: #include "../debug.h" truelight@9441: #include "../thread.h" truebrain@9859: #include "../fiber.hpp" rubidium@9724: #include "../player_func.h" truelight@9441: #include "ai.h" rubidium@9444: #include "ai_threads.h" truelight@9441: #include "api/ai_controller.hpp" truelight@9496: #include "api/ai_object.hpp" KUDr@9514: #include truelight@9441: truebrain@9859: class AIFiber : public SimpleCountedObject { truebrain@9859: private: truebrain@9859: typedef std::map > Fibers; truebrain@9859: truebrain@9859: /** Different states of the AI fiber. */ truebrain@9859: enum FiberState { rubidium@9444: INITIALIZED, ///< The mutex and conditions are initialized truebrain@9859: STARTING, ///< The fiber is just started rubidium@9444: RUNNING, ///< We are inside the main AI rubidium@9444: SUSPENDED, ///< We are suspended (either by sleep or block) rubidium@9444: STOPPING, ///< We are being stopped rubidium@9444: STOPPED, ///< The main AI has stopped rubidium@9444: }; rubidium@9444: truebrain@9859: public: rubidium@9444: /** truebrain@9859: * Initialize this AIFiber for the given controller. truebrain@9859: * @param player The PlayerID of this AI. truebrain@9859: * @param controller The AI we have to run. rubidium@9444: */ truebrain@9859: AIFiber(PlayerID player, AIController *controller) : truebrain@9859: fiber_id(player), truebrain@9859: state(INITIALIZED), truebrain@9859: ticks_to_sleep(0), truebrain@9859: controller(controller), truebrain@9859: fiber(NULL) rubidium@9444: { truebrain@9859: /* The player shouldn't have a fiber yet */ truebrain@9859: assert(s_fibers[player] == NULL); truebrain@9859: /* Register the new fiber */ truebrain@9859: s_fibers[player] = this; rubidium@9444: truebrain@9859: /* Ensure that main fiber has its wrapper too */ truebrain@9859: if (player != MAIN_FIBER) { truebrain@9859: stFind(MAIN_FIBER); truebrain@9859: } else { truebrain@9859: this->fiber = Fiber::AttachCurrent(this); truebrain@9859: assert(this->fiber->IsRunning()); truebrain@9859: } KUDr@9514: } KUDr@9514: truebrain@9859: ~AIFiber() KUDr@9514: { truebrain@9859: /* Make sure we are not in the active list anymore */ truebrain@9859: assert(this->fiber_id == MAIN_FIBER || stFind(this->fiber_id) == NULL); truebrain@9859: /* Clean up */ truebrain@9859: delete this->fiber; rubidium@9444: } rubidium@9444: rubidium@9444: /** truebrain@9859: * Start the AI fiber and return once the AI calls Sleep or any other truebrain@9859: * suspending command. rubidium@9444: */ truebrain@9859: bool Start() rubidium@9444: { truebrain@9859: /* Make sure we start fibers from the main fiber */ truebrain@9859: assert(stCurrentFiber() == stFind(MAIN_FIBER)); truebrain@9859: /* We shouldn't have a fiber yet */ truebrain@9859: assert(this->fiber == NULL); truebrain@9859: /* And we shouldn't be starting the main fiber */ truebrain@9859: assert(fiber_id != MAIN_FIBER); rubidium@9444: truebrain@9859: /* Create fiber for this AI */ truebrain@9859: this->fiber = Fiber::New(&stFiberProc, this); truebrain@9859: if (!this->fiber->IsRunning()) return false; rubidium@9444: truebrain@9859: /* Switch to STARTING mode */ truebrain@9859: this->state = SUSPENDED; truebrain@9859: this->SwitchToState(STARTING); truebrain@9859: truebrain@9859: return true; rubidium@9444: } rubidium@9444: rubidium@9444: /** truebrain@9859: * Suspend this AI for a 'timeout' of period. truebrain@9859: * @param timeout Time to suspend. < 0 means infinite (MultiPlayer only!) truebrain@9859: * @note Only call this from within the fiber you want to execute the function on. rubidium@9444: */ rubidium@9444: void Suspend(int timeout) rubidium@9444: { truebrain@9760: /* Should not attempt to suspend main fiber */ truebrain@9859: assert(this->fiber_id != MAIN_FIBER); truebrain@9760: /* AI fiber should be running */ truebrain@9760: assert(this->state == RUNNING); truebrain@9760: /* We can only suspend ourself */ truebrain@9859: assert(this->stCurrentFiber() == this); rubidium@9444: rubidium@9444: this->ticks_to_sleep = timeout; rubidium@9444: truebrain@9859: /* When AI fiber gets suspended, it always switches back to main fiber */ truebrain@9859: AIFiber *main = stFind(MAIN_FIBER); truebrain@9859: main->SwitchToState(RUNNING); truebrain@9760: truebrain@9859: /* The main fiber wants this AI to die */ KUDr@9514: if (this->state == STOPPING) { truebrain@11094: /* Exit the fiber */ truebrain@9859: this->Exit(); truebrain@9859: truebrain@9859: /* Here we will never come as... the fiber is dead */ truebrain@9859: assert(false); truebrain@9859: KUDr@9514: return; KUDr@9514: } truebrain@9859: KUDr@9514: assert(this->state == RUNNING); rubidium@9444: } rubidium@9444: rubidium@9444: /** truebrain@9859: * Set the AI fiber to resume at the next call of RunTick. truebrain@9859: * @note Only call this from within the main fiber. rubidium@9444: */ rubidium@9444: void Resume() rubidium@9444: { truebrain@9760: /* Should be called from main fiber only */ truebrain@9859: assert(stCurrentFiber() == stFind(MAIN_FIBER)); truebrain@9760: /* AI fiber should be suspended */ rubidium@9444: assert(this->state == SUSPENDED); truebrain@9859: truelight@9450: /* Normally the ticks_to_sleep hangs at -1 for MP. Possible the MP is KUDr@9514: * faster then the delay requested by the user. In this case the value KUDr@9514: * is lower. To let the normal delay system kick in, we reverse the value KUDr@9514: * of ticks_to_sleep. But now it doesn't directly continue when the value KUDr@9514: * was 'hanging', so we subtract 1 and it all works fine. */ truelight@9450: this->ticks_to_sleep = -this->ticks_to_sleep - 1; rubidium@9444: } rubidium@9444: rubidium@9444: /** truebrain@9859: * Let the AI fiber run for a while and return when it is done. truebrain@9859: * However, when the fiber is suspended and the suspend timeout has not yet truebrain@9859: * passed, nothing happens except decrementing the before mentioned timeout. truebrain@9859: * @note Only call this from within the main fiber. rubidium@9444: */ rubidium@9444: void RunTick() rubidium@9444: { truebrain@9848: /* If we already stopped, don't do anything */ truebrain@11094: if (this->state == STOPPING || this->state == STOPPED) return; truebrain@9848: truebrain@9760: /* Should be called from main fiber only */ truebrain@9859: assert(stCurrentFiber() == stFind(MAIN_FIBER)); truebrain@9760: /* Only AI fibers should be resumed this way */ truebrain@9859: assert(this != stFind(MAIN_FIBER)); KUDr@9514: /* AI fiber should be still suspended */ rubidium@9444: assert(this->state == SUSPENDED); KUDr@9514: rubidium@9444: this->controller->IncreaseTick(); rubidium@9444: truelight@9450: /* If the value is < -1, the user wants a delay which might exceed the delay truelight@9450: * of the MP. Therefor we keep adding value to ticks_to_sleep till it truelight@9450: * reaches -1, and we 'hang' it there infinitely, until the MP commands truelight@9450: * comes in. In the case it happens sooner, the Resume() codes handles it truelight@9450: * nicely and makes the value positive with the remaining ticks to wait. */ truebrain@9760: if (this->ticks_to_sleep < -1) this->ticks_to_sleep++; truebrain@9760: if (this->ticks_to_sleep < 0) return; // We have to wait infinitely rubidium@9444: if (--this->ticks_to_sleep > 0) return; // We have to wait a little longer rubidium@9444: truebrain@9859: /* Make the fiber running again */ truebrain@9859: this->SwitchToState(RUNNING); KUDr@9514: } KUDr@9514: KUDr@9514: /** truebrain@9859: * Try to stop the AI, and mark it as such. You can cleanup the AI after truebrain@9859: * this if you like; it won't be become active for sure. truebrain@9859: * @note There is no guarantee the fiber dies or anything, we just promise truebrain@9859: * it won't be called anymore. truebrain@9859: * @note Only call this from within the main fiber. KUDr@9514: */ truebrain@9859: void Stop() KUDr@9514: { truebrain@9859: /* Should be called from main fiber only */ truebrain@9859: assert(stCurrentFiber() == stFind(MAIN_FIBER)); truebrain@9848: /* If we already stopped, don't do anything */ truebrain@11094: if (this->state == STOPPING || this->state == STOPPED) return; truebrain@9848: truebrain@9859: /* Trigger a STOPPING round */ truebrain@9859: assert(this->state == SUSPENDED); truebrain@9859: this->SwitchToState(STOPPING); truebrain@11094: assert(this->state == STOPPING); KUDr@9514: truebrain@9859: /* Mark the fiber as STOPPED */ truebrain@9761: this->state = STOPPED; KUDr@9514: } KUDr@9514: KUDr@9514: /** truebrain@9859: * Find and return the fiber object by its 'id' (player_id or MAIN_FIBER). truebrain@9859: * Returns NULL if such fiber object doesn't exist. If truebrain@9859: * (fiber_id == MAIN_FIBER) and the fiber object with this id doesn't truebrain@9859: * exist, the new one is created and attached to the current (main) fiber. KUDr@9514: */ truebrain@9859: static AIFiber *stFind(int fiber_id) KUDr@9514: { truebrain@9859: Fibers::iterator it = s_fibers.find(fiber_id); truebrain@9859: if (it != s_fibers.end()) return (*it).second; truebrain@9859: if (fiber_id != MAIN_FIBER) return NULL; KUDr@9514: truebrain@9859: /* Main fiber doesn't have its own fiber object, create it */ truebrain@9859: return new AIFiber((PlayerID)MAIN_FIBER, NULL); KUDr@9514: } KUDr@9514: KUDr@9514: /** truebrain@9859: * Remove fiber object from the collection by its 'id' and destroy it. KUDr@9514: */ truebrain@9859: static void stRelease(int fiber_id) KUDr@9514: { truebrain@9859: Fibers::iterator it = s_fibers.find(fiber_id); truebrain@9859: if (it != s_fibers.end()) s_fibers.erase(it); KUDr@9514: } KUDr@9514: KUDr@9514: /** truebrain@9859: * Find the fiber object that belongs to the currently running fiber (caller). KUDr@9514: */ truebrain@9859: static AIFiber *stCurrentFiber() KUDr@9514: { truebrain@9859: AIFiber *cur = (AIFiber *)Fiber::GetCurrentFiberData(); KUDr@9514: assert(cur != NULL); KUDr@9514: return cur; KUDr@9514: } KUDr@9514: truebrain@9859: private: KUDr@9514: /** truebrain@9859: * Switch the state of a fiber. It becomes active and executed until it truebrain@9859: * changes the state. The current active fiber becomes SUSPENDED. truebrain@9859: * @note Only call this from an other fiber. KUDr@9514: */ truebrain@9859: void SwitchToState(FiberState new_state) KUDr@9514: { truebrain@9859: /* You can't switch the state of an already active fiber */ truebrain@9859: assert(stCurrentFiber() != this); truebrain@9859: /* The fiber should be suspended */ truebrain@9859: assert(this->state == SUSPENDED); truebrain@9859: /* And there should be an active fiber on it */ truebrain@9859: assert(this->fiber != NULL); truebrain@9859: truebrain@9859: /* Suspend the fiber */ truebrain@9859: stCurrentFiber()->state = SUSPENDED; truebrain@9859: truebrain@9859: /* Switch to new state, activate fiber, and check if we are suspended again */ truebrain@9859: this->state = new_state; truebrain@9859: this->fiber->SwitchToFiber(); truebrain@11094: assert(this->state == SUSPENDED || this->state == STOPPING); KUDr@9514: } KUDr@9514: truebrain@9859: /** truebrain@9859: * The method that runs in the new Fiber. Automaticly called when the Fiber becomes active. truebrain@9859: */ truebrain@9859: void FiberProc() truebrain@9859: { truebrain@9859: /* We should be starting */ truebrain@9859: assert(this->state == STARTING); truebrain@9859: /* We should be the active thread */ truebrain@9859: assert(stCurrentFiber() == this); truebrain@9859: truebrain@9859: /* Switch to running */ truebrain@9859: this->state = RUNNING; truebrain@9859: truebrain@10870: /* We want to skip the first tick, so switch back to the main fiber */ truebrain@10870: AIFiber *main = stFind(MAIN_FIBER); truebrain@10870: main->SwitchToState(RUNNING); truebrain@10870: truebrain@10875: if (this->state != STOPPING) { truebrain@10875: /* Start up the AI (this should be an infinite loop) */ truebrain@10875: this->controller->Start(); truebrain@9859: truebrain@10875: /* If we come here, the AI exited because it wanted to */ truebrain@10875: DEBUG(ai, 1, "We've got a suicidal AI for player %d", this->fiber_id); truebrain@9859: truebrain@10875: /* Wait until we are killed nicely by the game */ truebrain@10875: while (this->state != STOPPING) { truebrain@10875: main->SwitchToState(RUNNING); truebrain@10875: } truebrain@9859: } truebrain@9859: truebrain@11094: /* Stopping, and exit */ truebrain@11094: this->state = STOPPING; truebrain@9859: this->Exit(); truebrain@9859: truebrain@9859: /* Here we will never come as... the fiber is dead */ truebrain@9859: assert(false); truebrain@9859: } KUDr@9514: KUDr@9514: /** truebrain@9859: * Function that is started as process of the new AI fiber. KUDr@9514: */ truebrain@9859: static void CDECL stFiberProc(void *fiber) KUDr@9514: { truebrain@9859: AIFiber *cur = (AIFiber *)fiber; KUDr@9514: cur->FiberProc(); KUDr@9514: } KUDr@9514: KUDr@9514: /** truebrain@9859: * Exit a running fiber. truebrain@9859: * @note Only call this from within the fiber you want to execute the function on. KUDr@9514: */ truebrain@9859: void Exit() KUDr@9514: { truebrain@9859: /* We can only exit if we are the thread */ truebrain@9859: assert(stCurrentFiber() == this); truebrain@9859: /* We can never exit the main fiber */ truebrain@9859: assert(fiber_id != MAIN_FIBER); KUDr@9514: truebrain@9859: this->fiber->Exit(); KUDr@9514: } KUDr@9514: truebrain@9859: static Fibers s_fibers; ///< Collection of active fibers. truebrain@9859: static const int MAIN_FIBER = -1; KUDr@9514: truebrain@9859: int fiber_id; ///< id of this fiber (or MAIN_FIBER for the main). truebrain@9859: FiberState state; ///< The current state of the AI. truebrain@9859: int ticks_to_sleep; ///< Sleep this many runticks. truebrain@9859: AIController *controller; ///< Controller of this AI. truebrain@9859: Fiber *fiber; ///< Fiber we are connected to. KUDr@9514: }; KUDr@9514: truebrain@9859: /* static */ AIFiber::Fibers AIFiber::s_fibers; rubidium@9444: truelight@9441: /** truelight@9441: * Suspend the AI handler of a player. rubidium@9444: * @param player the player to suspend for rubidium@9444: * @param timeout Time to suspend. < 0 means infinite (MultiPlayer only!) truebrain@9859: * @note This should be called from within the fiber running this player! truelight@9441: */ rubidium@9444: void AI_SuspendPlayer(PlayerID player, int timeout) truelight@9441: { truebrain@10412: DEBUG(ai, 6, "AI_SuspendPlayer(%d, %d) from thr %d", player, timeout, ThreadObject::CurrentId()); truebrain@9859: AIFiber *thr = AIFiber::stCurrentFiber(); KUDr@9514: assert(thr != NULL); KUDr@9514: thr->Suspend(timeout); truelight@9441: } truelight@9441: truelight@9441: /** truebrain@9859: * Run the fiber of an AI player. Return when it is suspended again. rubidium@9444: * @param player the player to run this tick for truelight@9441: */ rubidium@9444: void AI_RunTick(PlayerID player) truelight@9441: { KUDr@9514: DEBUG(ai, 6, "AI_RunTick(%d) from thr %d", player, ThreadObject::CurrentId()); truebrain@9859: AIFiber *thr = AIFiber::stFind(player); KUDr@9514: if (thr == NULL) { KUDr@9514: DEBUG(ai, 0, "AI_RunTick() called for dead AI player #%d", player); KUDr@9514: return; KUDr@9514: } KUDr@9514: thr->RunTick(); truelight@9441: } truelight@9441: truelight@9441: /** truelight@9441: * Run an AI player for the first time. rubidium@9444: * @param player the (AI) player to start rubidium@9444: * @param controller the actual AI truelight@9441: */ truelight@9441: void AI_StartPlayer(PlayerID player, AIController *controller) truelight@9441: { truebrain@10412: DEBUG(ai, 6, "AI_StartPlayer(%d) from thr %d", player, ThreadObject::CurrentId()); truebrain@9859: AIFiber *thr = AIFiber::stFind(player); KUDr@9514: assert(thr == NULL); truebrain@9859: thr = new AIFiber(player, controller); KUDr@9514: thr->Start(); truelight@9441: } truelight@9441: truelight@9441: /** rubidium@9444: * Stops the given player rubidium@9444: * @param player the (AI) player to stop truelight@9441: */ rubidium@9444: void AI_StopPlayer(PlayerID player) truelight@9441: { truebrain@10412: DEBUG(ai, 6, "AI_StopPlayer(%d) from thr %d", player, ThreadObject::CurrentId()); truebrain@9859: AIFiber *thr = AIFiber::stFind(player); KUDr@9514: if (thr == NULL) return; KUDr@9514: thr->Stop(); truebrain@9859: AIFiber::stRelease(player); truelight@9441: } truelight@9441: truelight@9441: /** truelight@9441: * Callback for when a network command was executed. truelight@9441: * As commands are always ordered, we don't have to worry about right command. truelight@9441: */ truelight@9441: void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2) truelight@9441: { KUDr@9514: DEBUG(ai, 6, "CcAI(%d) from thr %d", _current_player, ThreadObject::CurrentId()); truebrain@9859: AIFiber *thr = AIFiber::stFind(_current_player); KUDr@9514: assert(thr != NULL); truebrain@9760: /* Store if we were a success or not */ truebrain@9760: AIObject::SetLastCommandRes(success); truelight@9496: truebrain@9859: /* Resume the fiber now */ KUDr@9514: thr->Resume(); truelight@9441: } truelight@9441: