Improve shared storage read / write

This commit is contained in:
Ted John 2020-03-07 12:37:07 +00:00
parent 0c71886941
commit ea8890aaaa
6 changed files with 179 additions and 140 deletions

View File

@ -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

View File

@ -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

View File

@ -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.
};
/**

View File

@ -14,147 +14,172 @@
# include <cstdio>
# include <dukglue/dukglue.h>
# include <duktape.h>
# include <optional>
# include <stdexcept>
template<typename T> DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr<T>& value)
namespace OpenRCT2::Scripting
{
dukglue::types::DukType<std::shared_ptr<T>>::template push<T>(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<typename T> 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<typename T> DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr<T>& value)
{
dukglue::types::DukType<std::shared_ptr<T>>::template push<T>(ctx, value);
return DukValue::take_from_stack(ctx);
}
DukObject(const DukObject&) = delete;
template<typename T> 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<DukValue> 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

View File

@ -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<GameActionResult> 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<GameActionResult>();
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<GameActionResult> ScriptEngine::QueryOrExecuteCustomGameAction(
{
auto action = std::make_unique<GameActionResult>();
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&)

View File

@ -200,6 +200,7 @@ namespace OpenRCT2::Scripting
DukValue GameActionResultToDuk(const GameAction& action, const std::unique_ptr<GameActionResult>& result);
DukValue PositionToDuk(const CoordsXYZ& position);
void InitSharedStorage();
void LoadSharedStorage();
};