diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 1e91933e97..027396a382 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -57,6 +57,7 @@ #include "ride/TrackDesignRepository.h" #include "scenario/Scenario.h" #include "scenario/ScenarioRepository.h" +#include "scripting/ScriptEngine.h" #include "title/TitleScreen.h" #include "title/TitleSequenceManager.h" #include "ui/UiContext.h" @@ -76,6 +77,7 @@ using namespace OpenRCT2::Audio; using namespace OpenRCT2::Drawing; using namespace OpenRCT2::Localisation; using namespace OpenRCT2::Paint; +using namespace OpenRCT2::Scripting; using namespace OpenRCT2::Ui; namespace OpenRCT2 @@ -100,6 +102,7 @@ namespace OpenRCT2 std::unique_ptr _discordService; #endif StdInOutConsole _stdInOutConsole; + ScriptEngine _scriptEngine; // Game states std::unique_ptr _titleScreen; @@ -135,6 +138,7 @@ namespace OpenRCT2 , _uiContext(uiContext) , _localisationService(std::make_unique(env)) , _painter(std::make_unique(uiContext)) + , _scriptEngine(_stdInOutConsole, *env) { // Can't have more than one context currently. Guard::Assert(Instance == nullptr); @@ -176,6 +180,11 @@ namespace OpenRCT2 return _uiContext; } + Scripting::ScriptEngine& GetScriptEngine() override + { + return _scriptEngine; + } + GameState* GetGameState() override { return _gameState.get(); @@ -1015,7 +1024,7 @@ namespace OpenRCT2 Twitch::Update(); chat_update(); - _stdInOutConsole.ProcessEvalQueue(); + _scriptEngine.Update(); _uiContext->Update(); } diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 4b4193e652..c8ee6181ed 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -85,6 +85,11 @@ namespace OpenRCT2 class LocalisationService; } + namespace Scripting + { + class ScriptEngine; + } + namespace Ui { interface IUiContext; @@ -109,6 +114,7 @@ namespace OpenRCT2 virtual Localisation::LocalisationService& GetLocalisationService() abstract; virtual IObjectManager& GetObjectManager() abstract; virtual IObjectRepository& GetObjectRepository() abstract; + virtual Scripting::ScriptEngine& GetScriptEngine() abstract; virtual ITrackDesignRepository* GetTrackDesignRepository() abstract; virtual IScenarioRepository* GetScenarioRepository() abstract; virtual IReplayManager* GetReplayManager() abstract; diff --git a/src/openrct2/interface/InteractiveConsole.h b/src/openrct2/interface/InteractiveConsole.h index ec1a3181d4..ce35d74e1d 100644 --- a/src/openrct2/interface/InteractiveConsole.h +++ b/src/openrct2/interface/InteractiveConsole.h @@ -12,11 +12,8 @@ #include "../common.h" #include "../localisation/FormatCodes.h" -#include #include -#include #include -#include struct rct_drawpixelinfo; struct TextInputSession; @@ -53,13 +50,9 @@ public: class StdInOutConsole final : public InteractiveConsole { -private: - std::queue, std::string>> _evalQueue; - public: void Start(); std::future Eval(const std::string& s); - void ProcessEvalQueue(); void Clear() override; void Close() override; diff --git a/src/openrct2/interface/StdInOutConsole.cpp b/src/openrct2/interface/StdInOutConsole.cpp index df6c2359f3..9491b00cd7 100644 --- a/src/openrct2/interface/StdInOutConsole.cpp +++ b/src/openrct2/interface/StdInOutConsole.cpp @@ -7,11 +7,15 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ +#include "../Context.h" #include "../OpenRCT2.h" #include "../platform/Platform2.h" +#include "../scripting/ScriptEngine.h" #include "../thirdparty/linenoise.hpp" #include "InteractiveConsole.h" +using namespace OpenRCT2; + void StdInOutConsole::Start() { std::thread replThread([this]() -> void { @@ -51,28 +55,8 @@ void StdInOutConsole::Start() std::future StdInOutConsole::Eval(const std::string& s) { - // Push on-demand evaluations onto a queue so that it can be processed deterministically - // on the main thead at the right time. - std::promise barrier; - auto future = barrier.get_future(); - _evalQueue.emplace(std::move(barrier), s); - return future; -} - -void StdInOutConsole::ProcessEvalQueue() -{ - while (_evalQueue.size() > 0) - { - auto item = std::move(_evalQueue.front()); - _evalQueue.pop(); - auto promise = std::move(std::get<0>(item)); - auto command = std::move(std::get<1>(item)); - - Execute(command); - - // Signal the promise so caller can continue - promise.set_value(); - } + auto& scriptEngine = GetContext()->GetScriptEngine(); + return scriptEngine.Eval(s); } void StdInOutConsole::Clear() diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp new file mode 100644 index 0000000000..e3b6f89959 --- /dev/null +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -0,0 +1,79 @@ +/***************************************************************************** + * Copyright (c) 2014-2018 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "ScriptEngine.h" +#include "../interface/InteractiveConsole.h" +#include +#include + +using namespace OpenRCT2; +using namespace OpenRCT2::Scripting; + +static std::string Stringify(duk_context * ctx, duk_idx_t idx); + +ScriptEngine::ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env) : + _console(console), + _env(env) +{ + _context = duk_create_heap_default(); + if (_context == nullptr) + { + throw std::runtime_error("Unable to initialise duktape context."); + } +} + +ScriptEngine::~ScriptEngine() +{ + duk_destroy_heap(_context); +} + +void ScriptEngine::Update() +{ + while (_evalQueue.size() > 0) + { + auto item = std::move(_evalQueue.front()); + _evalQueue.pop(); + auto promise = std::move(std::get<0>(item)); + auto command = std::move(std::get<1>(item)); + if (duk_peval_string(_context, command.c_str()) != 0) + { + std::string result = std::string(duk_safe_to_string(_context, -1)); + _console.WriteLineError(result); + } + else + { + std::string result = Stringify(_context, -1); + _console.WriteLine(result); + } + duk_pop(_context); + // Signal the promise so caller can continue + promise.set_value(); + } +} + +std::future ScriptEngine::Eval(const std::string &s) +{ + std::promise barrier; + auto future = barrier.get_future(); + _evalQueue.emplace(std::move(barrier), s); + return future; +} + +static std::string Stringify(duk_context * ctx, duk_idx_t idx) +{ + auto type = duk_get_type(ctx, idx); + if (type == DUK_TYPE_OBJECT && !duk_is_function(ctx, idx)) + { + return duk_json_encode(ctx, idx); + } + else + { + return duk_safe_to_string(ctx, idx); + } +} diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h new file mode 100644 index 0000000000..e04550da91 --- /dev/null +++ b/src/openrct2/scripting/ScriptEngine.h @@ -0,0 +1,45 @@ +/***************************************************************************** + * Copyright (c) 2014-2018 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "../common.h" +#include +#include +#include + +struct duk_hthread; +typedef struct duk_hthread duk_context; + +class InteractiveConsole; + +namespace OpenRCT2 +{ + interface IPlatformEnvironment; +} + +namespace OpenRCT2::Scripting +{ + class ScriptEngine + { + private: + InteractiveConsole& _console; + IPlatformEnvironment& _env; + duk_context * _context; + std::queue, std::string>> _evalQueue; + + public: + ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env); + ScriptEngine(ScriptEngine&&) = delete; + ~ScriptEngine(); + + void Update(); + std::future Eval(const std::string &s); + }; +}