From ea8890aaaa391edb877efe3489dc5e5270811483 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 7 Mar 2020 12:37:07 +0000 Subject: [PATCH] Improve shared storage read / write --- distribution/openrct2.d.ts | 5 - src/openrct2/PlatformEnvironment.cpp | 3 +- src/openrct2/PlatformEnvironment.h | 1 + src/openrct2/scripting/Duktape.hpp | 255 +++++++++++++----------- src/openrct2/scripting/ScriptEngine.cpp | 54 +++-- src/openrct2/scripting/ScriptEngine.h | 1 + 6 files changed, 179 insertions(+), 140 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 13b4cf10cc..a108a60dac 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -116,11 +116,6 @@ declare global { */ sharedStorage: Configuration; - /** - * Local generic storage for a each plugin. - */ - localStorage: Configuration; - /** * Gets a random integer within the specified range using the game's pseudo- * random number generator. This is part of the game state and shared across diff --git a/src/openrct2/PlatformEnvironment.cpp b/src/openrct2/PlatformEnvironment.cpp index 8c5f781301..4b25f4de76 100644 --- a/src/openrct2/PlatformEnvironment.cpp +++ b/src/openrct2/PlatformEnvironment.cpp @@ -240,6 +240,7 @@ const char * PlatformEnvironment::FileNames[] = "highscores.dat", // SCORES "scores.dat", // SCORES (LEGACY) "Saved Games" PATH_SEPARATOR "scores.dat", // SCORES (RCT2) - "changelog.txt" // CHANGELOG + "changelog.txt", // CHANGELOG + "plugin.store.json" // PLUGIN_STORE }; // clang-format on diff --git a/src/openrct2/PlatformEnvironment.h b/src/openrct2/PlatformEnvironment.h index a17974e455..a7024e2921 100644 --- a/src/openrct2/PlatformEnvironment.h +++ b/src/openrct2/PlatformEnvironment.h @@ -66,6 +66,7 @@ namespace OpenRCT2 SCORES_LEGACY, // Scenario scores, legacy (scores.dat). SCORES_RCT2, // Scenario scores, rct2 (\Saved Games\scores.dat). CHANGELOG, // Notable changes to the game between versions, distributed with the game. + PLUGIN_STORE, // Shared storage for plugins. }; /** diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index bead6995f2..06a3c7c516 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -14,147 +14,172 @@ # include # include # include +# include # include -template DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr& value) +namespace OpenRCT2::Scripting { - dukglue::types::DukType>::template push(ctx, value); - return DukValue::take_from_stack(ctx); -} - -template T AsOrDefault(const DukValue& value, const T& defaultValue = {}) = delete; - -template<> inline std::string AsOrDefault(const DukValue& value, const std::string& defaultValue) -{ - return value.type() == DukValue::STRING ? value.as_string() : defaultValue; -} - -template<> inline int32_t AsOrDefault(const DukValue& value, const int32_t& defaultValue) -{ - return value.type() == DukValue::NUMBER ? value.as_int() : defaultValue; -} - -/** - * Allows creation of an object on the duktape stack and setting properties on it before - * retrieving the DukValue instance of it. - */ -class DukObject -{ -private: - duk_context* _ctx{}; - duk_idx_t _idx = DUK_INVALID_INDEX; - -public: - DukObject(duk_context* ctx) - : _ctx(ctx) + template DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr& value) { + dukglue::types::DukType>::template push(ctx, value); + return DukValue::take_from_stack(ctx); } - DukObject(const DukObject&) = delete; + template T AsOrDefault(const DukValue& value, const T& defaultValue = {}) = delete; - DukObject(DukObject&& m) noexcept + template<> inline std::string AsOrDefault(const DukValue& value, const std::string& defaultValue) { - _ctx = m._ctx; - _idx = m._idx; - m._ctx = {}; - m._idx = {}; + return value.type() == DukValue::STRING ? value.as_string() : defaultValue; } - ~DukObject() + template<> inline int32_t AsOrDefault(const DukValue& value, const int32_t& defaultValue) { - PopObjectIfExists(); + return value.type() == DukValue::NUMBER ? value.as_int() : defaultValue; } - void Set(const char* name, bool value) + /** + * Allows creation of an object on the duktape stack and setting properties on it before + * retrieving the DukValue instance of it. + */ + class DukObject { - EnsureObjectPushed(); - duk_push_boolean(_ctx, value); - duk_put_prop_string(_ctx, _idx, name); - } + private: + duk_context* _ctx{}; + duk_idx_t _idx = DUK_INVALID_INDEX; - void Set(const char* name, int32_t value) - { - EnsureObjectPushed(); - duk_push_int(_ctx, value); - duk_put_prop_string(_ctx, _idx, name); - } - - void Set(const char* name, uint32_t value) - { - EnsureObjectPushed(); - duk_push_uint(_ctx, value); - duk_put_prop_string(_ctx, _idx, name); - } - - void Set(const char* name, const std::string_view& value) - { - EnsureObjectPushed(); - duk_push_lstring(_ctx, value.data(), value.size()); - duk_put_prop_string(_ctx, _idx, name); - } - - void Set(const char* name, const DukValue& value) - { - EnsureObjectPushed(); - value.push(); - duk_put_prop_string(_ctx, _idx, name); - } - - DukValue Take() - { - EnsureObjectPushed(); - auto result = DukValue::take_from_stack(_ctx, _idx); - _idx = DUK_INVALID_INDEX; - return result; - } - -private: - void PopObjectIfExists() - { - if (_idx != DUK_INVALID_INDEX) + public: + DukObject(duk_context* ctx) + : _ctx(ctx) { - duk_remove(_ctx, _idx); + } + + DukObject(const DukObject&) = delete; + + DukObject(DukObject&& m) noexcept + { + _ctx = m._ctx; + _idx = m._idx; + m._ctx = {}; + m._idx = {}; + } + + ~DukObject() + { + PopObjectIfExists(); + } + + void Set(const char* name, bool value) + { + EnsureObjectPushed(); + duk_push_boolean(_ctx, value); + duk_put_prop_string(_ctx, _idx, name); + } + + void Set(const char* name, int32_t value) + { + EnsureObjectPushed(); + duk_push_int(_ctx, value); + duk_put_prop_string(_ctx, _idx, name); + } + + void Set(const char* name, uint32_t value) + { + EnsureObjectPushed(); + duk_push_uint(_ctx, value); + duk_put_prop_string(_ctx, _idx, name); + } + + void Set(const char* name, const std::string_view& value) + { + EnsureObjectPushed(); + duk_push_lstring(_ctx, value.data(), value.size()); + duk_put_prop_string(_ctx, _idx, name); + } + + void Set(const char* name, const DukValue& value) + { + EnsureObjectPushed(); + value.push(); + duk_put_prop_string(_ctx, _idx, name); + } + + DukValue Take() + { + EnsureObjectPushed(); + auto result = DukValue::take_from_stack(_ctx, _idx); _idx = DUK_INVALID_INDEX; + return result; } - } - void EnsureObjectPushed() - { - if (_idx == DUK_INVALID_INDEX) + private: + void PopObjectIfExists() { - _idx = duk_push_object(_ctx); + if (_idx != DUK_INVALID_INDEX) + { + duk_remove(_ctx, _idx); + _idx = DUK_INVALID_INDEX; + } } - } -}; -class DukStackFrame -{ -private: - duk_context* _ctx{}; - duk_idx_t _top; - -public: - DukStackFrame(duk_context* ctx) - : _ctx(ctx) - { - _top = duk_get_top(ctx); - } - - ~DukStackFrame() - { - auto top = duk_get_top(_ctx); - if (top != _top) + void EnsureObjectPushed() { - duk_set_top(_ctx, _top); + if (_idx == DUK_INVALID_INDEX) + { + _idx = duk_push_object(_ctx); + } + } + }; + + class DukStackFrame + { + private: + duk_context* _ctx{}; + duk_idx_t _top; + + public: + DukStackFrame(duk_context* ctx) + : _ctx(ctx) + { + _top = duk_get_top(ctx); + } + + ~DukStackFrame() + { + auto top = duk_get_top(_ctx); + if (top != _top) + { + duk_set_top(_ctx, _top); + _ctx = {}; + std::fprintf(stderr, "duktape stack was not returned to original state!"); + // assert(false); + } _ctx = {}; - std::fprintf(stderr, "duktape stack was not returned to original state!"); - // assert(false); } - _ctx = {}; + + DukStackFrame(const DukStackFrame&) = delete; + DukStackFrame(DukStackFrame&&) = delete; + }; + + inline duk_ret_t duk_json_decode_wrapper(duk_context* ctx, void*) + { + duk_json_decode(ctx, -1); + return 1; } - DukStackFrame(const DukStackFrame&) = delete; - DukStackFrame(DukStackFrame&&) = delete; -}; + inline std::optional DuktapeTryParseJson(duk_context* ctx, const std::string_view& json) + { + duk_push_lstring(ctx, json.data(), json.size()); + if (duk_safe_call(ctx, duk_json_decode_wrapper, nullptr, 1, 1) == DUK_EXEC_SUCCESS) + { + return DukValue::take_from_stack(ctx); + } + else + { + // Pop error off stack + duk_pop(ctx); + return std::nullopt; + } + } +} // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index dbfa8fdd45..af4b9d5968 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -394,9 +394,7 @@ void ScriptEngine::Initialise() _pluginsLoaded = false; _pluginsStarted = false; - duk_push_object(ctx); - _sharedStorage = std::move(DukValue::take_from_stack(ctx)); - LoadSharedStorage(); + InitSharedStorage(); } void ScriptEngine::LoadPlugins() @@ -505,7 +503,7 @@ void ScriptEngine::SetupHotReloading() } catch (const std::exception& e) { - std::printf("Unable to enable hot reloading of plugins: %s\n", e.what()); + std::fprintf(stderr, "Unable to enable hot reloading of plugins: %s\n", e.what()); } } @@ -555,6 +553,8 @@ void ScriptEngine::UnloadPlugins() void ScriptEngine::StartPlugins() { + LoadSharedStorage(); + for (auto& plugin : _plugins) { if (!plugin->HasStarted() && ShouldStartPlugin(plugin)) @@ -713,19 +713,25 @@ std::unique_ptr ScriptEngine::QueryOrExecuteCustomGameAction( // Deserialise the JSON args std::string argsz(args); - duk_push_string(_context, argsz.c_str()); - duk_json_decode(_context, -1); - auto dukArgs = DukValue::take_from_stack(_context); + + auto dukArgs = DuktapeTryParseJson(_context, argsz); + if (!dukArgs) + { + auto action = std::make_unique(); + action->Error = GA_ERROR::INVALID_PARAMETERS; + action->ErrorTitle = "Invalid JSON"; + return action; + } // Ready to call plugin handler DukValue dukResult; if (!isExecute) { - dukResult = ExecutePluginCall(customAction.Owner, customAction.Query, { dukArgs }, false); + dukResult = ExecutePluginCall(customAction.Owner, customAction.Query, { *dukArgs }, false); } else { - dukResult = ExecutePluginCall(customAction.Owner, customAction.Execute, { dukArgs }, true); + dukResult = ExecutePluginCall(customAction.Owner, customAction.Execute, { *dukArgs }, true); } return DukToGameActionResult(dukResult); } @@ -733,7 +739,7 @@ std::unique_ptr ScriptEngine::QueryOrExecuteCustomGameAction( { auto action = std::make_unique(); action->Error = GA_ERROR::UNKNOWN; - action->ErrorTitle = OBJECT_ERROR_UNKNOWN; + action->ErrorTitle = "Unknown custom action"; return action; } } @@ -864,15 +870,25 @@ void ScriptEngine::RunGameActionHooks(const GameAction& action, std::unique_ptr< } } +void ScriptEngine::InitSharedStorage() +{ + duk_push_object(_context); + _sharedStorage = std::move(DukValue::take_from_stack(_context)); +} + void ScriptEngine::LoadSharedStorage() { - std::string path = "C:\\Users\\Ted\\Documents\\OpenRCT2\\plugin.json"; + InitSharedStorage(); + + auto path = _env.GetFilePath(PATHID::PLUGIN_STORE); try { auto data = File::ReadAllBytes(path); - duk_push_lstring(_context, (const char*)data.data(), data.size()); - duk_json_decode(_context, -1); - _sharedStorage = std::move(DukValue::take_from_stack(_context)); + auto result = DuktapeTryParseJson(_context, std::string_view((const char*)data.data(), data.size())); + if (result) + { + _sharedStorage = std::move(*result); + } } catch (const std::exception&) { @@ -881,13 +897,13 @@ void ScriptEngine::LoadSharedStorage() void ScriptEngine::SaveSharedStorage() { - _sharedStorage.push(); - auto json = std::string(duk_json_encode(_context, -1)); - duk_pop(_context); - - std::string path = "C:\\Users\\Ted\\Documents\\OpenRCT2\\plugin.json"; + auto path = _env.GetFilePath(PATHID::PLUGIN_STORE); try { + _sharedStorage.push(); + auto json = std::string(duk_json_encode(_context, -1)); + duk_pop(_context); + File::WriteAllBytes(path, json.c_str(), json.size()); } catch (const std::exception&) diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index c390b3220a..d54ad8d3d2 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -200,6 +200,7 @@ namespace OpenRCT2::Scripting DukValue GameActionResultToDuk(const GameAction& action, const std::unique_ptr& result); DukValue PositionToDuk(const CoordsXYZ& position); + void InitSharedStorage(); void LoadSharedStorage(); };