Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/rlinterface/RLInterface.cpp
Show All 16 Lines | |||||
// Pull in the headers from the default precompiled header, | // Pull in the headers from the default precompiled header, | ||||
// even if rlinterface doesn't use precompiled headers. | // even if rlinterface doesn't use precompiled headers. | ||||
#include "lib/precompiled.h" | #include "lib/precompiled.h" | ||||
#include "rlinterface/RLInterface.h" | #include "rlinterface/RLInterface.h" | ||||
#include "gui/GUIManager.h" | #include "gui/GUIManager.h" | ||||
#include "ps/CLogger.h" | |||||
#include "ps/Game.h" | #include "ps/Game.h" | ||||
#include "ps/Loader.h" | #include "ps/Loader.h" | ||||
#include "ps/CLogger.h" | #include "ps/GameSetup/GameSetup.h" | ||||
#include "simulation2/Simulation2.h" | |||||
#include "simulation2/components/ICmpAIInterface.h" | #include "simulation2/components/ICmpAIInterface.h" | ||||
#include "simulation2/components/ICmpTemplateManager.h" | #include "simulation2/components/ICmpTemplateManager.h" | ||||
#include "simulation2/Simulation2.h" | |||||
#include "simulation2/system/LocalTurnManager.h" | #include "simulation2/system/LocalTurnManager.h" | ||||
#include "third_party/mongoose/mongoose.h" | |||||
#include <queue> | |||||
#include <sstream> | #include <sstream> | ||||
#include <tuple> | |||||
// Globally accessible pointer to the RL Interface. | // Globally accessible pointer to the RL Interface. | ||||
RLInterface* g_RLInterface = nullptr; | std::unique_ptr<RL::Interface> g_RLInterface; | ||||
namespace RL | |||||
{ | |||||
Interface::Interface(const char* server_address) : m_GameMessage({GameMessageType::None}) | |||||
{ | |||||
LOGMESSAGERENDER("Starting RL interface HTTP server"); | |||||
const char *options[] = { | |||||
"listening_ports", server_address, | |||||
"num_threads", "1", | |||||
nullptr | |||||
}; | |||||
mg_context* mgContext = mg_start(MgCallback, this, options); | |||||
ENSURE(mgContext); | |||||
} | |||||
// Interactions with the game engine (g_Game) must be done in the main | // Interactions with the game engine (g_Game) must be done in the main | ||||
// thread as there are specific checks for this. We will pass our commands | // thread as there are specific checks for this. We will pass messages | ||||
// to the main thread to be applied | // to the main thread to be applied (ie, "GameMessage"s). | ||||
std::string RLInterface::SendGameMessage(const GameMessage msg) | std::string Interface::SendGameMessage(GameMessage&& msg) | ||||
{ | { | ||||
std::unique_lock<std::mutex> msgLock(m_msgLock); | std::unique_lock<std::mutex> msgLock(m_MsgLock); | ||||
m_GameMessage = &msg; | ENSURE(m_GameMessage.type == GameMessageType::None); | ||||
m_msgApplied.wait(msgLock); | m_GameMessage = std::move(msg); | ||||
m_MsgApplied.wait(msgLock, [this]() { return m_GameMessage.type == GameMessageType::None; }); | |||||
return m_GameState; | return m_GameState; | ||||
} | } | ||||
std::string RLInterface::Step(const std::vector<Command> commands) | std::string Interface::Step(std::vector<GameCommand>&& commands) | ||||
{ | { | ||||
std::lock_guard<std::mutex> lock(m_lock); | std::lock_guard<std::mutex> lock(m_Lock); | ||||
GameMessage msg = { GameMessageType::Commands, commands }; | return SendGameMessage({ GameMessageType::Commands, std::move(commands) }); | ||||
return SendGameMessage(msg); | |||||
} | } | ||||
std::string RLInterface::Reset(const ScenarioConfig* scenario) | std::string Interface::Reset(ScenarioConfig&& scenario) | ||||
{ | { | ||||
std::lock_guard<std::mutex> lock(m_lock); | std::lock_guard<std::mutex> lock(m_Lock); | ||||
m_ScenarioConfig = *scenario; | m_ScenarioConfig = std::move(scenario); | ||||
struct GameMessage msg = { GameMessageType::Reset }; | return SendGameMessage({ GameMessageType::Reset }); | ||||
return SendGameMessage(msg); | |||||
} | } | ||||
std::vector<std::string> RLInterface::GetTemplates(const std::vector<std::string> names) const | std::vector<std::string> Interface::GetTemplates(const std::vector<std::string>& names) const | ||||
{ | { | ||||
std::lock_guard<std::mutex> lock(m_lock); | std::lock_guard<std::mutex> lock(m_Lock); | ||||
CSimulation2& simulation = *g_Game->GetSimulation2(); | CSimulation2& simulation = *g_Game->GetSimulation2(); | ||||
CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation.GetSimContext().GetSystemEntity()); | CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation.GetSimContext().GetSystemEntity()); | ||||
std::vector<std::string> templates; | std::vector<std::string> templates; | ||||
for (const std::string& templateName : names) | for (const std::string& templateName : names) | ||||
{ | { | ||||
const CParamNode* node = cmpTemplateManager->GetTemplate(templateName); | const CParamNode* node = cmpTemplateManager->GetTemplate(templateName); | ||||
if (node != nullptr) | if (node != nullptr) | ||||
{ | templates.push_back(utf8_from_wstring(node->ToXML())); | ||||
std::string content = utf8_from_wstring(node->ToXML()); | |||||
templates.push_back(content); | |||||
} | |||||
} | } | ||||
return templates; | return templates; | ||||
} | } | ||||
static void* RLMgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info) | void* Interface::MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info) | ||||
{ | { | ||||
RLInterface* interface = (RLInterface*)request_info->user_data; | Interface* interface = (Interface*)request_info->user_data; | ||||
ENSURE(interface); | ENSURE(interface); | ||||
void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling | void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling | ||||
const char* header200 = | const char* header200 = | ||||
"HTTP/1.1 200 OK\r\n" | "HTTP/1.1 200 OK\r\n" | ||||
"Access-Control-Allow-Origin: *\r\n" | "Access-Control-Allow-Origin: *\r\n" | ||||
"Content-Type: text/plain; charset=utf-8\r\n\r\n"; | "Content-Type: text/plain; charset=utf-8\r\n\r\n"; | ||||
Show All 14 Lines | const char* notRunningResponse = | ||||
"Game not running. Please create a scenario first."; | "Game not running. Please create a scenario first."; | ||||
switch (event) | switch (event) | ||||
{ | { | ||||
case MG_NEW_REQUEST: | case MG_NEW_REQUEST: | ||||
{ | { | ||||
std::stringstream stream; | std::stringstream stream; | ||||
std::string uri = request_info->uri; | const std::string uri = request_info->uri; | ||||
if (uri == "/reset") | if (uri == "/reset") | ||||
{ | { | ||||
const char* val = mg_get_header(conn, "Content-Length"); | const char* val = mg_get_header(conn, "Content-Length"); | ||||
if (!val) | if (!val) | ||||
{ | { | ||||
mg_printf(conn, "%s", noPostData); | mg_printf(conn, "%s", noPostData); | ||||
return handled; | return handled; | ||||
} | } | ||||
ScenarioConfig scenario; | ScenarioConfig scenario; | ||||
std::string qs(request_info->query_string); | const std::string qs(request_info->query_string); | ||||
scenario.saveReplay = qs.find("saveReplay") != std::string::npos; | scenario.saveReplay = qs.find("saveReplay") != std::string::npos; | ||||
scenario.playerID = 1; | scenario.playerID = 1; | ||||
char playerID[1]; | char playerID[1]; | ||||
int len = mg_get_var(request_info->query_string, qs.length(), "playerID", playerID, 1); | const int len = mg_get_var(request_info->query_string, qs.length(), "playerID", playerID, 1); | ||||
if (len != -1) | if (len != -1) | ||||
scenario.playerID = std::stoi(playerID); | scenario.playerID = std::stoi(playerID); | ||||
int bufSize = std::atoi(val); | const int bufSize = std::atoi(val); | ||||
std::unique_ptr<char> buf = std::unique_ptr<char>(new char[bufSize]); | std::unique_ptr<char[]> buf = std::unique_ptr<char[]>(new char[bufSize]); | ||||
mg_read(conn, buf.get(), bufSize); | mg_read(conn, buf.get(), bufSize); | ||||
std::string content(buf.get(), bufSize); | const std::string content(buf.get(), bufSize); | ||||
scenario.content = content; | scenario.content = content; | ||||
std::string gameState = interface->Reset(&scenario); | const std::string gameState = interface->Reset(std::move(scenario)); | ||||
stream << gameState.c_str(); | stream << gameState.c_str(); | ||||
} | } | ||||
else if (uri == "/step") | else if (uri == "/step") | ||||
{ | { | ||||
if (!interface->IsGameRunning()) | if (!interface->IsGameRunning()) | ||||
{ | { | ||||
mg_printf(conn, "%s", notRunningResponse); | mg_printf(conn, "%s", notRunningResponse); | ||||
return handled; | return handled; | ||||
} | } | ||||
const char* val = mg_get_header(conn, "Content-Length"); | const char* val = mg_get_header(conn, "Content-Length"); | ||||
if (!val) | if (!val) | ||||
{ | { | ||||
mg_printf(conn, "%s", noPostData); | mg_printf(conn, "%s", noPostData); | ||||
return handled; | return handled; | ||||
} | } | ||||
int bufSize = std::atoi(val); | int bufSize = std::atoi(val); | ||||
std::unique_ptr<char> buf = std::unique_ptr<char>(new char[bufSize]); | std::unique_ptr<char[]> buf = std::unique_ptr<char[]>(new char[bufSize]); | ||||
mg_read(conn, buf.get(), bufSize); | mg_read(conn, buf.get(), bufSize); | ||||
std::string postData(buf.get(), bufSize); | const std::string postData(buf.get(), bufSize); | ||||
std::stringstream postStream(postData); | std::stringstream postStream(postData); | ||||
std::string line; | std::string line; | ||||
std::vector<Command> commands; | std::vector<GameCommand> commands; | ||||
while (std::getline(postStream, line, '\n')) | while (std::getline(postStream, line, '\n')) | ||||
{ | { | ||||
Command cmd; | GameCommand cmd; | ||||
const std::size_t splitPos = line.find(";"); | const std::size_t splitPos = line.find(";"); | ||||
if (splitPos != std::string::npos) | if (splitPos != std::string::npos) | ||||
{ | { | ||||
cmd.playerID = std::stoi(line.substr(0, splitPos)); | cmd.playerID = std::stoi(line.substr(0, splitPos)); | ||||
cmd.json_cmd = line.substr(splitPos + 1); | cmd.json_cmd = line.substr(splitPos + 1); | ||||
commands.push_back(cmd); | commands.push_back(cmd); | ||||
} | } | ||||
} | } | ||||
std::string gameState = interface->Step(commands); | const std::string gameState = interface->Step(std::move(commands)); | ||||
if (gameState.empty()) | if (gameState.empty()) | ||||
{ | { | ||||
mg_printf(conn, "%s", notRunningResponse); | mg_printf(conn, "%s", notRunningResponse); | ||||
return handled; | return handled; | ||||
} | } | ||||
else | else | ||||
stream << gameState.c_str(); | stream << gameState.c_str(); | ||||
} | } | ||||
else if (uri == "/templates") | else if (uri == "/templates") | ||||
{ | { | ||||
if (!interface->IsGameRunning()) { | if (!interface->IsGameRunning()) { | ||||
mg_printf(conn, "%s", notRunningResponse); | mg_printf(conn, "%s", notRunningResponse); | ||||
return handled; | return handled; | ||||
} | } | ||||
const char* val = mg_get_header(conn, "Content-Length"); | const char* val = mg_get_header(conn, "Content-Length"); | ||||
if (!val) | if (!val) | ||||
{ | { | ||||
mg_printf(conn, "%s", noPostData); | mg_printf(conn, "%s", noPostData); | ||||
return handled; | return handled; | ||||
} | } | ||||
int bufSize = std::atoi(val); | const int bufSize = std::atoi(val); | ||||
std::unique_ptr<char> buf = std::unique_ptr<char>(new char[bufSize]); | std::unique_ptr<char[]> buf = std::unique_ptr<char[]>(new char[bufSize]); | ||||
mg_read(conn, buf.get(), bufSize); | mg_read(conn, buf.get(), bufSize); | ||||
std::string postData(buf.get(), bufSize); | const std::string postData(buf.get(), bufSize); | ||||
std::stringstream postStream(postData); | std::stringstream postStream(postData); | ||||
std::string line; | std::string line; | ||||
std::vector<std::string> templateNames; | std::vector<std::string> templateNames; | ||||
while (std::getline(postStream, line, '\n')) | while (std::getline(postStream, line, '\n')) | ||||
templateNames.push_back(line); | templateNames.push_back(line); | ||||
for (std::string templateStr : interface->GetTemplates(templateNames)) | for (std::string templateStr : interface->GetTemplates(templateNames)) | ||||
stream << templateStr.c_str() << "\n"; | stream << templateStr.c_str() << "\n"; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
mg_printf(conn, "%s", header404); | mg_printf(conn, "%s", header404); | ||||
return handled; | return handled; | ||||
} | } | ||||
mg_printf(conn, "%s", header200); | mg_printf(conn, "%s", header200); | ||||
std::string str = stream.str(); | const std::string str = stream.str(); | ||||
mg_write(conn, str.c_str(), str.length()); | mg_write(conn, str.c_str(), str.length()); | ||||
return handled; | return handled; | ||||
} | } | ||||
case MG_HTTP_ERROR: | case MG_HTTP_ERROR: | ||||
return nullptr; | return nullptr; | ||||
case MG_EVENT_LOG: | case MG_EVENT_LOG: | ||||
// Called by Mongoose's cry() | // Called by Mongoose's cry() | ||||
LOGERROR("Mongoose error: %s", request_info->log_message); | LOGERROR("Mongoose error: %s", request_info->log_message); | ||||
return nullptr; | return nullptr; | ||||
case MG_INIT_SSL: | case MG_INIT_SSL: | ||||
return nullptr; | return nullptr; | ||||
default: | default: | ||||
debug_warn(L"Invalid Mongoose event type"); | debug_warn(L"Invalid Mongoose event type"); | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
}; | }; | ||||
void RLInterface::EnableHTTP(const char* server_address) | bool Interface::TryGetGameMessage(GameMessage& msg) | ||||
{ | { | ||||
LOGMESSAGERENDER("Starting RL interface HTTP server"); | if (m_GameMessage.type != GameMessageType::None) | ||||
// Ignore multiple enablings | |||||
if (m_MgContext) | |||||
return; | |||||
const char *options[] = { | |||||
"listening_ports", server_address, | |||||
"num_threads", "6", // enough for the browser's parallel connection limit | |||||
nullptr | |||||
}; | |||||
m_MgContext = mg_start(RLMgCallback, this, options); | |||||
ENSURE(m_MgContext); | |||||
} | |||||
bool RLInterface::TryGetGameMessage(GameMessage& msg) | |||||
{ | { | ||||
if (m_GameMessage != nullptr) { | msg = m_GameMessage; | ||||
msg = *m_GameMessage; | m_GameMessage = {GameMessageType::None}; | ||||
m_GameMessage = nullptr; | |||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
void RLInterface::TryApplyMessage() | void Interface::TryApplyMessage() | ||||
{ | { | ||||
const bool nonVisual = !g_GUI; | |||||
const bool isGameStarted = g_Game && g_Game->IsGameStarted(); | const bool isGameStarted = g_Game && g_Game->IsGameStarted(); | ||||
if (m_NeedsGameState && isGameStarted) | if (m_NeedsGameState && isGameStarted) | ||||
{ | { | ||||
m_GameState = GetGameState(); | m_GameState = GetGameState(); | ||||
m_msgApplied.notify_one(); | m_MsgApplied.notify_one(); | ||||
m_msgLock.unlock(); | m_MsgLock.unlock(); | ||||
m_NeedsGameState = false; | m_NeedsGameState = false; | ||||
} | } | ||||
if (m_msgLock.try_lock()) | if (!m_MsgLock.try_lock()) | ||||
{ | return; | ||||
GameMessage msg; | GameMessage msg; | ||||
if (TryGetGameMessage(msg)) { | if (!TryGetGameMessage(msg)) | ||||
{ | |||||
m_MsgLock.unlock(); | |||||
return; | |||||
} | |||||
ApplyMessage(msg); | |||||
} | |||||
void Interface::ApplyMessage(const GameMessage& msg) | |||||
{ | |||||
const static std::string EMPTY_STATE; | |||||
const bool nonVisual = !g_GUI; | |||||
const bool isGameStarted = g_Game && g_Game->IsGameStarted(); | |||||
switch (msg.type) | switch (msg.type) | ||||
{ | { | ||||
case GameMessageType::Reset: | case GameMessageType::Reset: | ||||
{ | { | ||||
if (isGameStarted) | if (isGameStarted) | ||||
EndGame(); | EndGame(); | ||||
g_Game = new CGame(m_ScenarioConfig.saveReplay); | g_Game = new CGame(m_ScenarioConfig.saveReplay); | ||||
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); | ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); | ||||
ScriptRequest rq(scriptInterface); | ScriptRequest rq(scriptInterface); | ||||
JS::RootedValue attrs(rq.cx); | JS::RootedValue attrs(rq.cx); | ||||
scriptInterface.ParseJSON(m_ScenarioConfig.content, &attrs); | scriptInterface.ParseJSON(m_ScenarioConfig.content, &attrs); | ||||
g_Game->SetPlayerID(m_ScenarioConfig.playerID); | g_Game->SetPlayerID(m_ScenarioConfig.playerID); | ||||
g_Game->StartGame(&attrs, ""); | g_Game->StartGame(&attrs, ""); | ||||
if (nonVisual) | if (nonVisual) | ||||
{ | { | ||||
LDR_NonprogressiveLoad(); | LDR_NonprogressiveLoad(); | ||||
ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK); | ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK); | ||||
m_GameState = GetGameState(); | m_GameState = GetGameState(); | ||||
m_msgApplied.notify_one(); | m_MsgApplied.notify_one(); | ||||
m_msgLock.unlock(); | m_MsgLock.unlock(); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
JS::RootedValue initData(rq.cx); | JS::RootedValue initData(rq.cx); | ||||
scriptInterface.CreateObject(rq, &initData); | scriptInterface.CreateObject(rq, &initData); | ||||
scriptInterface.SetProperty(initData, "attribs", attrs); | scriptInterface.SetProperty(initData, "attribs", attrs); | ||||
JS::RootedValue playerAssignments(rq.cx); | JS::RootedValue playerAssignments(rq.cx); | ||||
scriptInterface.CreateObject(rq, &playerAssignments); | scriptInterface.CreateObject(rq, &playerAssignments); | ||||
scriptInterface.SetProperty(initData, "playerAssignments", playerAssignments); | scriptInterface.SetProperty(initData, "playerAssignments", playerAssignments); | ||||
g_GUI->SwitchPage(L"page_loading.xml", &scriptInterface, initData); | g_GUI->SwitchPage(L"page_loading.xml", &scriptInterface, initData); | ||||
m_NeedsGameState = true; | m_NeedsGameState = true; | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
case GameMessageType::Commands: | case GameMessageType::Commands: | ||||
{ | { | ||||
if (!g_Game) | if (!g_Game) | ||||
{ | { | ||||
m_GameState = EMPTY_STATE; | m_GameState = EMPTY_STATE; | ||||
m_msgApplied.notify_one(); | m_MsgApplied.notify_one(); | ||||
m_msgLock.unlock(); | m_MsgLock.unlock(); | ||||
return; | return; | ||||
} | } | ||||
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); | |||||
CLocalTurnManager* turnMgr = static_cast<CLocalTurnManager*>(g_Game->GetTurnManager()); | CLocalTurnManager* turnMgr = static_cast<CLocalTurnManager*>(g_Game->GetTurnManager()); | ||||
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); | for (const GameCommand& command : msg.commands) | ||||
ScriptRequest rq(scriptInterface); | |||||
for (Command command : msg.commands) | |||||
{ | { | ||||
ScriptRequest rq(scriptInterface); | |||||
JS::RootedValue commandJSON(rq.cx); | JS::RootedValue commandJSON(rq.cx); | ||||
scriptInterface.ParseJSON(command.json_cmd, &commandJSON); | scriptInterface.ParseJSON(command.json_cmd, &commandJSON); | ||||
turnMgr->PostCommand(command.playerID, commandJSON); | turnMgr->PostCommand(command.playerID, commandJSON); | ||||
} | } | ||||
const double deltaRealTime = DEFAULT_TURN_LENGTH_SP; | const u32 deltaRealTime = DEFAULT_TURN_LENGTH_SP; | ||||
if (nonVisual) | if (nonVisual) | ||||
{ | { | ||||
const double deltaSimTime = deltaRealTime * g_Game->GetSimRate(); | const double deltaSimTime = deltaRealTime * g_Game->GetSimRate(); | ||||
size_t maxTurns = static_cast<size_t>(g_Game->GetSimRate()); | const size_t maxTurns = static_cast<size_t>(g_Game->GetSimRate()); | ||||
g_Game->GetTurnManager()->Update(deltaSimTime, maxTurns); | g_Game->GetTurnManager()->Update(deltaSimTime, maxTurns); | ||||
} | } | ||||
else | else | ||||
g_Game->Update(deltaRealTime); | g_Game->Update(deltaRealTime); | ||||
m_GameState = GetGameState(); | m_GameState = GetGameState(); | ||||
m_msgApplied.notify_one(); | m_MsgApplied.notify_one(); | ||||
m_msgLock.unlock(); | m_MsgLock.unlock(); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
else | |||||
m_msgLock.unlock(); | |||||
} | |||||
} | |||||
std::string RLInterface::GetGameState() | std::string Interface::GetGameState() const | ||||
{ | { | ||||
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); | const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); | ||||
ScriptRequest rq(scriptInterface); | |||||
const CSimContext simContext = g_Game->GetSimulation2()->GetSimContext(); | const CSimContext simContext = g_Game->GetSimulation2()->GetSimContext(); | ||||
CmpPtr<ICmpAIInterface> cmpAIInterface(simContext.GetSystemEntity()); | CmpPtr<ICmpAIInterface> cmpAIInterface(simContext.GetSystemEntity()); | ||||
ScriptRequest rq(scriptInterface); | |||||
JS::RootedValue state(rq.cx); | JS::RootedValue state(rq.cx); | ||||
cmpAIInterface->GetFullRepresentation(&state, true); | cmpAIInterface->GetFullRepresentation(&state, true); | ||||
return scriptInterface.StringifyJSON(&state, false); | return scriptInterface.StringifyJSON(&state, false); | ||||
} | } | ||||
bool RLInterface::IsGameRunning() | bool Interface::IsGameRunning() const | ||||
{ | { | ||||
return !!g_Game; | return g_Game != nullptr; | ||||
} | |||||
} | } |
Wildfire Games · Phabricator