Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/rlinterface/RLInterface.h
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2020 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#ifndef INCLUDED_RLINTERFACE | #ifndef INCLUDED_RLINTERFACE | ||||
#define INCLUDED_RLINTERFACE | #define INCLUDED_RLINTERFACE | ||||
#include "simulation2/helpers/Player.h" | #include "simulation2/helpers/Player.h" | ||||
#include "third_party/mongoose/mongoose.h" | |||||
#include <condition_variable> | #include <condition_variable> | ||||
#include <mutex> | #include <mutex> | ||||
#include <string> | |||||
#include <vector> | #include <vector> | ||||
struct ScenarioConfig { | namespace RL | ||||
{ | |||||
struct ScenarioConfig | |||||
{ | |||||
bool saveReplay; | bool saveReplay; | ||||
player_id_t playerID; | player_id_t playerID; | ||||
std::string content; | std::string content; | ||||
}; | }; | ||||
struct Command { | |||||
struct GameCommand | |||||
{ | |||||
int playerID; | int playerID; | ||||
std::string json_cmd; | std::string json_cmd; | ||||
}; | }; | ||||
enum GameMessageType { Reset, Commands }; | enum class GameMessageType | ||||
struct GameMessage { | { | ||||
None, | |||||
Reset, | |||||
Commands, | |||||
}; | |||||
/** | |||||
* Holds messages from the RL client to the game. | |||||
*/ | |||||
struct GameMessage | |||||
{ | |||||
GameMessageType type; | GameMessageType type; | ||||
std::vector<Command> commands; | std::vector<GameCommand> commands; | ||||
}; | }; | ||||
extern void EndGame(); | /** | ||||
* Implements an interface providing fundamental capabilities required for reinforcement | |||||
* learning (over HTTP). | |||||
* | |||||
* This consists of enabling an external script to configure the scenario (via Reset) and | |||||
* then step the game engine manually and apply player actions (via Step). The interface | |||||
* also supports querying unit templates to provide information about max health and other | |||||
* potentially relevant game state information. | |||||
* | |||||
* See source/tools/rlclient/ for the external client code. | |||||
* | |||||
* The HTTP server is threaded. | |||||
* Flow of data (with the interface active): | |||||
* 0. The game/main thread calls TryApplyMessage() | |||||
* - If no messages are pending, GOTO 0 (the simulation is not advanced). | |||||
* 1. TryApplyMessage locks m_MsgLock, pulls the message, processes it, advances the simulation, and sets m_GameState. | |||||
* 2. TryApplyMessage notifies the RL thread that it can carry on and unlocks m_MsgLock. The main thread carries on frame rendering and goes back to 0. | |||||
* 3. The RL thread locks m_MsgLock, reads m_GameState, unlocks m_MsgLock, and sends the gamestate as HTTP Response to the RL client. | |||||
* 4. The client processes the response and ultimately sends a new HTTP message to the RL Interface. | |||||
* 5. The RL thread locks m_MsgLock, pushes the message, and starts waiting on the game/main thread to notify it (step 2). | |||||
* - GOTO 0. | |||||
*/ | |||||
class Interface | |||||
{ | |||||
NONCOPYABLE(Interface); | |||||
public: | |||||
Interface(const char* server_address); | |||||
struct mg_context; | /** | ||||
const static std::string EMPTY_STATE; | * Non-blocking call to process any pending messages from the RL client. | ||||
* Updates m_GameState to the gamestate after messages have been processed. | |||||
*/ | |||||
void TryApplyMessage(); | |||||
class RLInterface | private: | ||||
{ | static void* MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info); | ||||
public: | /** | ||||
* Process commands, update the simulation by one turn. | |||||
* @return the gamestate after processing commands. | |||||
*/ | |||||
std::string Step(std::vector<GameCommand>&& commands); | |||||
/** | |||||
* Reset the game state according to scenario, cleaning up existing games if required. | |||||
* @return the gamestate after resetting. | |||||
*/ | |||||
std::string Reset(ScenarioConfig&& scenario); | |||||
/** | |||||
* @return template data for all templates of @param names. | |||||
*/ | |||||
std::vector<std::string> GetTemplates(const std::vector<std::string>& names) const; | |||||
std::string Step(const std::vector<Command> commands); | /** | ||||
std::string Reset(const ScenarioConfig* scenario); | * @return true if a game is currently running. | ||||
std::vector<std::string> GetTemplates(const std::vector<std::string> names) const; | */ | ||||
bool IsGameRunning() const; | |||||
void EnableHTTP(const char* server_address); | /** | ||||
std::string SendGameMessage(const GameMessage msg); | * Internal helper. Move @param msg into m_GameMessage, wait until it has been processed by the main thread, | ||||
* and @return the gamestate after that message is processed. | |||||
* It is invalid to call this if m_GameMessage is not currently empty. | |||||
*/ | |||||
std::string SendGameMessage(GameMessage&& msg); | |||||
/** | |||||
* Internal helper. | |||||
* @return true if m_GameMessage is not empty, and updates @param msg, false otherwise (msg is then unchanged). | |||||
*/ | |||||
bool TryGetGameMessage(GameMessage& msg); | bool TryGetGameMessage(GameMessage& msg); | ||||
void TryApplyMessage(); | |||||
std::string GetGameState(); | /** | ||||
bool IsGameRunning(); | * Process any pending messages from the RL client. | ||||
* Updates m_GameState to the gamestate after messages have been processed. | |||||
*/ | |||||
void ApplyMessage(const GameMessage& msg); | |||||
/** | |||||
* @return the full gamestate as a JSON strong. | |||||
* This uses the AI representation since it is readily available in the JS Engine. | |||||
*/ | |||||
std::string GetGameState() const; | |||||
private: | private: | ||||
mg_context* m_MgContext = nullptr; | GameMessage m_GameMessage; | ||||
const GameMessage* m_GameMessage = nullptr; | ScenarioConfig m_ScenarioConfig; | ||||
std::string m_GameState; | std::string m_GameState; | ||||
bool m_NeedsGameState = false; | bool m_NeedsGameState = false; | ||||
mutable std::mutex m_lock; | |||||
std::mutex m_msgLock; | mutable std::mutex m_Lock; | ||||
std::condition_variable m_msgApplied; | std::mutex m_MsgLock; | ||||
ScenarioConfig m_ScenarioConfig; | std::condition_variable m_MsgApplied; | ||||
}; | }; | ||||
extern RLInterface* g_RLInterface; | } | ||||
extern std::unique_ptr<RL::Interface> g_RLInterface; | |||||
#endif // INCLUDED_RLINTERFACE | #endif // INCLUDED_RLINTERFACE |
Wildfire Games · Phabricator