Address concerns in 5473393e30 (RL interface)

Fixes 5473393e30.

This:
- Puts RL code under the RL namespace to avoid name collisions
- Avoids usage of new/delete
- Adds more documentation
- Does general cleanup (const-correctness, move semantics, argument
types, early returns...)

Patch by: irishninja
Comments by: Vladislavbelov, wraitii
Differential Revision: https://code.wildfiregames.com/D2947
This was SVN commit r24631.
This commit is contained in:
wraitii 2021-01-15 16:30:05 +00:00
parent 6282960991
commit 6a67e9a52d
6 changed files with 297 additions and 251 deletions

View File

@ -74,7 +74,7 @@ that of Atlas depending on commandline parameters.
#include "graphics/TextureManager.h"
#include "gui/GUIManager.h"
#include "renderer/Renderer.h"
#include "rlinterface/RLInterface.cpp"
#include "rlinterface/RLInterface.h"
#include "scriptinterface/ScriptEngine.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/TurnManager.h"
@ -492,57 +492,10 @@ static void StartRLInterface(CmdLineArgs args)
if (!args.Get("rl-interface").empty())
server_address = args.Get("rl-interface");
g_RLInterface = new RLInterface();
g_RLInterface->EnableHTTP(server_address.c_str());
g_RLInterface = std::make_unique<RL::Interface>(server_address.c_str());
debug_printf("RL interface listening on %s\n", server_address.c_str());
}
static void RunRLServer(const bool isNonVisual, const std::vector<OsPath> modsToInstall, const CmdLineArgs args)
{
int flags = INIT_MODS;
while (!Init(args, flags))
{
flags &= ~INIT_MODS;
Shutdown(SHUTDOWN_FROM_CONFIG);
}
g_Shutdown = ShutdownType::None;
std::vector<CStr> installedMods;
if (!modsToInstall.empty())
{
Paths paths(args);
CModInstaller installer(paths.UserData() / "mods", paths.Cache());
// Install the mods without deleting the pyromod files
for (const OsPath& modPath : modsToInstall)
installer.Install(modPath, g_ScriptContext, true);
installedMods = installer.GetInstalledMods();
}
if (isNonVisual)
{
InitNonVisual(args);
StartRLInterface(args);
while (g_Shutdown == ShutdownType::None)
g_RLInterface->TryApplyMessage();
QuitEngine();
}
else
{
InitGraphics(args, 0, installedMods);
MainControllerInit();
StartRLInterface(args);
while (g_Shutdown == ShutdownType::None)
Frame();
}
Shutdown(0);
MainControllerShutdown();
CXeromyces::Terminate();
delete g_RLInterface;
}
// moved into a helper function to ensure args is destroyed before
// exit(), which may result in a memory leak.
static void RunGameOrAtlas(int argc, const char* argv[])
@ -569,6 +522,7 @@ static void RunGameOrAtlas(int argc, const char* argv[])
const bool isVisualReplay = args.Has("replay-visual");
const bool isNonVisualReplay = args.Has("replay");
const bool isNonVisual = args.Has("autostart-nonvisual");
const bool isUsingRLInterface = args.Has("rl-interface");
const OsPath replayFile(
isVisualReplay ? args.Get("replay-visual") :
@ -681,12 +635,6 @@ static void RunGameOrAtlas(int argc, const char* argv[])
const double res = timer_Resolution();
g_frequencyFilter = CreateFrequencyFilter(res, 30.0);
if (args.Has("rl-interface"))
{
RunRLServer(isNonVisual, modsToInstall, args);
return;
}
// run the game
int flags = INIT_MODS;
do
@ -716,13 +664,23 @@ static void RunGameOrAtlas(int argc, const char* argv[])
if (isNonVisual)
{
InitNonVisual(args);
if (isUsingRLInterface)
StartRLInterface(args);
while (g_Shutdown == ShutdownType::None)
NonVisualFrame();
{
if (isUsingRLInterface)
g_RLInterface->TryApplyMessage();
else
NonVisualFrame();
}
}
else
{
InitGraphics(args, 0, installedMods);
MainControllerInit();
if (isUsingRLInterface)
StartRLInterface(args);
while (g_Shutdown == ShutdownType::None)
Frame();
}

View File

@ -35,6 +35,8 @@ extern bool ShouldRender();
**/
extern void EarlyInit();
extern void EndGame();
enum InitFlags
{
// avoid setting a video mode / initializing OpenGL; assume that has

View File

@ -26,13 +26,12 @@
#include "ps/Game.h"
#include "ps/Replay.h"
#include "ps/World.h"
#include "ps/GameSetup/GameSetup.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/system/TurnManager.h"
#include "simulation2/Simulation2.h"
#include "soundmanager/SoundManager.h"
extern void EndGame();
bool JSI_Game::IsGameStarted(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate))
{
return g_Game;

View File

@ -22,49 +22,65 @@
#include "rlinterface/RLInterface.h"
#include "gui/GUIManager.h"
#include "ps/CLogger.h"
#include "ps/Game.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/ICmpTemplateManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/LocalTurnManager.h"
#include "third_party/mongoose/mongoose.h"
#include <queue>
#include <sstream>
#include <tuple>
// 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
// thread as there are specific checks for this. We will pass our commands
// to the main thread to be applied
std::string RLInterface::SendGameMessage(const GameMessage msg)
// thread as there are specific checks for this. We will pass messages
// to the main thread to be applied (ie, "GameMessage"s).
std::string Interface::SendGameMessage(GameMessage&& msg)
{
std::unique_lock<std::mutex> msgLock(m_msgLock);
m_GameMessage = &msg;
m_msgApplied.wait(msgLock);
std::unique_lock<std::mutex> msgLock(m_MsgLock);
ENSURE(m_GameMessage.type == GameMessageType::None);
m_GameMessage = std::move(msg);
m_MsgApplied.wait(msgLock, [this]() { return m_GameMessage.type == GameMessageType::None; });
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);
GameMessage msg = { GameMessageType::Commands, commands };
return SendGameMessage(msg);
std::lock_guard<std::mutex> lock(m_Lock);
return SendGameMessage({ GameMessageType::Commands, std::move(commands) });
}
std::string RLInterface::Reset(const ScenarioConfig* scenario)
std::string Interface::Reset(ScenarioConfig&& scenario)
{
std::lock_guard<std::mutex> lock(m_lock);
m_ScenarioConfig = *scenario;
struct GameMessage msg = { GameMessageType::Reset };
return SendGameMessage(msg);
std::lock_guard<std::mutex> lock(m_Lock);
m_ScenarioConfig = std::move(scenario);
return SendGameMessage({ GameMessageType::Reset });
}
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();
CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation.GetSimContext().GetSystemEntity());
@ -74,18 +90,15 @@ std::vector<std::string> RLInterface::GetTemplates(const std::vector<std::string
const CParamNode* node = cmpTemplateManager->GetTemplate(templateName);
if (node != nullptr)
{
std::string content = utf8_from_wstring(node->ToXML());
templates.push_back(content);
}
templates.push_back(utf8_from_wstring(node->ToXML()));
}
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);
void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
@ -116,7 +129,7 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
{
std::stringstream stream;
std::string uri = request_info->uri;
const std::string uri = request_info->uri;
if (uri == "/reset")
{
@ -127,22 +140,22 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
return handled;
}
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.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)
scenario.playerID = std::stoi(playerID);
int bufSize = std::atoi(val);
std::unique_ptr<char> buf = std::unique_ptr<char>(new char[bufSize]);
const int bufSize = std::atoi(val);
std::unique_ptr<char[]> buf = std::unique_ptr<char[]>(new char[bufSize]);
mg_read(conn, buf.get(), bufSize);
std::string content(buf.get(), bufSize);
const std::string content(buf.get(), bufSize);
scenario.content = content;
std::string gameState = interface->Reset(&scenario);
const std::string gameState = interface->Reset(std::move(scenario));
stream << gameState.c_str();
}
@ -161,16 +174,16 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
return handled;
}
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);
std::string postData(buf.get(), bufSize);
const std::string postData(buf.get(), bufSize);
std::stringstream postStream(postData);
std::string line;
std::vector<Command> commands;
std::vector<GameCommand> commands;
while (std::getline(postStream, line, '\n'))
{
Command cmd;
GameCommand cmd;
const std::size_t splitPos = line.find(";");
if (splitPos != std::string::npos)
{
@ -179,7 +192,7 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
commands.push_back(cmd);
}
}
std::string gameState = interface->Step(commands);
const std::string gameState = interface->Step(std::move(commands));
if (gameState.empty())
{
mg_printf(conn, "%s", notRunningResponse);
@ -200,10 +213,10 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
mg_printf(conn, "%s", noPostData);
return handled;
}
int bufSize = std::atoi(val);
std::unique_ptr<char> buf = std::unique_ptr<char>(new char[bufSize]);
const int bufSize = std::atoi(val);
std::unique_ptr<char[]> buf = std::unique_ptr<char[]>(new char[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::string line;
std::vector<std::string> templateNames;
@ -221,7 +234,7 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
}
mg_printf(conn, "%s", header200);
std::string str = stream.str();
const std::string str = stream.str();
mg_write(conn, str.c_str(), str.length());
return handled;
}
@ -243,146 +256,137 @@ static void* RLMgCallback(mg_event event, struct mg_connection *conn, const stru
}
};
void RLInterface::EnableHTTP(const char* server_address)
bool Interface::TryGetGameMessage(GameMessage& msg)
{
LOGMESSAGERENDER("Starting RL interface HTTP server");
// 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;
m_GameMessage = nullptr;
if (m_GameMessage.type != GameMessageType::None)
{
msg = m_GameMessage;
m_GameMessage = {GameMessageType::None};
return true;
}
return false;
}
void RLInterface::TryApplyMessage()
void Interface::TryApplyMessage()
{
const bool nonVisual = !g_GUI;
const bool isGameStarted = g_Game && g_Game->IsGameStarted();
if (m_NeedsGameState && isGameStarted)
{
m_GameState = GetGameState();
m_msgApplied.notify_one();
m_msgLock.unlock();
m_MsgApplied.notify_one();
m_MsgLock.unlock();
m_NeedsGameState = false;
}
if (m_msgLock.try_lock())
if (!m_MsgLock.try_lock())
return;
GameMessage msg;
if (!TryGetGameMessage(msg))
{
GameMessage msg;
if (TryGetGameMessage(msg)) {
switch (msg.type)
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)
{
case GameMessageType::Reset:
{
if (isGameStarted)
EndGame();
g_Game = new CGame(m_ScenarioConfig.saveReplay);
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
ScriptRequest rq(scriptInterface);
JS::RootedValue attrs(rq.cx);
scriptInterface.ParseJSON(m_ScenarioConfig.content, &attrs);
g_Game->SetPlayerID(m_ScenarioConfig.playerID);
g_Game->StartGame(&attrs, "");
if (nonVisual)
{
case GameMessageType::Reset:
{
if (isGameStarted)
EndGame();
g_Game = new CGame(m_ScenarioConfig.saveReplay);
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
ScriptRequest rq(scriptInterface);
JS::RootedValue attrs(rq.cx);
scriptInterface.ParseJSON(m_ScenarioConfig.content, &attrs);
g_Game->SetPlayerID(m_ScenarioConfig.playerID);
g_Game->StartGame(&attrs, "");
if (nonVisual)
{
LDR_NonprogressiveLoad();
ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK);
m_GameState = GetGameState();
m_msgApplied.notify_one();
m_msgLock.unlock();
}
else
{
JS::RootedValue initData(rq.cx);
scriptInterface.CreateObject(rq, &initData);
scriptInterface.SetProperty(initData, "attribs", attrs);
JS::RootedValue playerAssignments(rq.cx);
scriptInterface.CreateObject(rq, &playerAssignments);
scriptInterface.SetProperty(initData, "playerAssignments", playerAssignments);
g_GUI->SwitchPage(L"page_loading.xml", &scriptInterface, initData);
m_NeedsGameState = true;
}
break;
}
case GameMessageType::Commands:
{
if (!g_Game)
{
m_GameState = EMPTY_STATE;
m_msgApplied.notify_one();
m_msgLock.unlock();
return;
}
CLocalTurnManager* turnMgr = static_cast<CLocalTurnManager*>(g_Game->GetTurnManager());
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
ScriptRequest rq(scriptInterface);
for (Command command : msg.commands)
{
JS::RootedValue commandJSON(rq.cx);
scriptInterface.ParseJSON(command.json_cmd, &commandJSON);
turnMgr->PostCommand(command.playerID, commandJSON);
}
const double deltaRealTime = DEFAULT_TURN_LENGTH_SP;
if (nonVisual)
{
const double deltaSimTime = deltaRealTime * g_Game->GetSimRate();
size_t maxTurns = static_cast<size_t>(g_Game->GetSimRate());
g_Game->GetTurnManager()->Update(deltaSimTime, maxTurns);
}
else
g_Game->Update(deltaRealTime);
m_GameState = GetGameState();
m_msgApplied.notify_one();
m_msgLock.unlock();
break;
}
LDR_NonprogressiveLoad();
ENSURE(g_Game->ReallyStartGame() == PSRETURN_OK);
m_GameState = GetGameState();
m_MsgApplied.notify_one();
m_MsgLock.unlock();
}
else
{
JS::RootedValue initData(rq.cx);
scriptInterface.CreateObject(rq, &initData);
scriptInterface.SetProperty(initData, "attribs", attrs);
JS::RootedValue playerAssignments(rq.cx);
scriptInterface.CreateObject(rq, &playerAssignments);
scriptInterface.SetProperty(initData, "playerAssignments", playerAssignments);
g_GUI->SwitchPage(L"page_loading.xml", &scriptInterface, initData);
m_NeedsGameState = true;
}
break;
}
case GameMessageType::Commands:
{
if (!g_Game)
{
m_GameState = EMPTY_STATE;
m_MsgApplied.notify_one();
m_MsgLock.unlock();
return;
}
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
CLocalTurnManager* turnMgr = static_cast<CLocalTurnManager*>(g_Game->GetTurnManager());
for (const GameCommand& command : msg.commands)
{
ScriptRequest rq(scriptInterface);
JS::RootedValue commandJSON(rq.cx);
scriptInterface.ParseJSON(command.json_cmd, &commandJSON);
turnMgr->PostCommand(command.playerID, commandJSON);
}
const u32 deltaRealTime = DEFAULT_TURN_LENGTH_SP;
if (nonVisual)
{
const double deltaSimTime = deltaRealTime * g_Game->GetSimRate();
const size_t maxTurns = static_cast<size_t>(g_Game->GetSimRate());
g_Game->GetTurnManager()->Update(deltaSimTime, maxTurns);
}
else
g_Game->Update(deltaRealTime);
m_GameState = GetGameState();
m_MsgApplied.notify_one();
m_MsgLock.unlock();
break;
}
else
m_msgLock.unlock();
}
}
std::string RLInterface::GetGameState()
std::string Interface::GetGameState() const
{
const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
ScriptRequest rq(scriptInterface);
const CSimContext simContext = g_Game->GetSimulation2()->GetSimContext();
CmpPtr<ICmpAIInterface> cmpAIInterface(simContext.GetSystemEntity());
ScriptRequest rq(scriptInterface);
JS::RootedValue state(rq.cx);
cmpAIInterface->GetFullRepresentation(&state, true);
return scriptInterface.StringifyJSON(&state, false);
}
bool RLInterface::IsGameRunning()
bool Interface::IsGameRunning() const
{
return !!g_Game;
return g_Game != nullptr;
}
}

View File

@ -14,64 +14,145 @@
* 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/>.
*/
#ifndef INCLUDED_RLINTERFACE
#define INCLUDED_RLINTERFACE
#include "simulation2/helpers/Player.h"
#include "third_party/mongoose/mongoose.h"
#include <condition_variable>
#include <mutex>
#include <string>
#include <vector>
struct ScenarioConfig {
namespace RL
{
struct ScenarioConfig
{
bool saveReplay;
player_id_t playerID;
std::string content;
};
struct Command {
struct GameCommand
{
int playerID;
std::string json_cmd;
};
enum GameMessageType { Reset, Commands };
struct GameMessage {
GameMessageType type;
std::vector<Command> commands;
};
extern void EndGame();
struct mg_context;
const static std::string EMPTY_STATE;
class RLInterface
enum class GameMessageType
{
public:
std::string Step(const std::vector<Command> commands);
std::string Reset(const ScenarioConfig* scenario);
std::vector<std::string> GetTemplates(const std::vector<std::string> names) const;
void EnableHTTP(const char* server_address);
std::string SendGameMessage(const GameMessage msg);
bool TryGetGameMessage(GameMessage& msg);
void TryApplyMessage();
std::string GetGameState();
bool IsGameRunning();
private:
mg_context* m_MgContext = nullptr;
const GameMessage* m_GameMessage = nullptr;
std::string m_GameState;
bool m_NeedsGameState = false;
mutable std::mutex m_lock;
std::mutex m_msgLock;
std::condition_variable m_msgApplied;
ScenarioConfig m_ScenarioConfig;
None,
Reset,
Commands,
};
extern RLInterface* g_RLInterface;
/**
* Holds messages from the RL client to the game.
*/
struct GameMessage
{
GameMessageType type;
std::vector<GameCommand> commands;
};
/**
* 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);
/**
* 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();
private:
static void* MgCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info);
/**
* 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;
/**
* @return true if a game is currently running.
*/
bool IsGameRunning() const;
/**
* 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);
/**
* 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:
GameMessage m_GameMessage;
ScenarioConfig m_ScenarioConfig;
std::string m_GameState;
bool m_NeedsGameState = false;
mutable std::mutex m_Lock;
std::mutex m_MsgLock;
std::condition_variable m_MsgApplied;
};
}
extern std::unique_ptr<RL::Interface> g_RLInterface;
#endif // INCLUDED_RLINTERFACE

View File

@ -23,6 +23,8 @@
#include "js/Initialization.h"
#include <list>
/**
* A class using the RAII (Resource Acquisition Is Initialization) idiom to manage initialization
* and shutdown of the SpiderMonkey script engine. It also keeps a count of active script contexts