Change API for interacting with park storage

This commit is contained in:
Ted John 2022-02-23 21:52:38 +00:00
parent 1182ff5f19
commit ed8b7cb6ee
4 changed files with 111 additions and 31 deletions

View File

@ -186,15 +186,18 @@ declare global {
sharedStorage: Configuration;
/**
* Shared generic storage for all plugins. Data is persisted for the current
* loaded park, and is stored inside the .park file. Any references to objects,
* or arrays are copied by reference. If these arrays, objects, or any other
* arrays, or objects that they reference change without a subsequent call to
* Gets the storage for the current plugin if no name is specified.
* If a plugin name is specified, the storage for the plugin with that name will be returned.
* Data is persisted for the current loaded park, and is stored inside the .park file.
* Any references to objects, or arrays are copied by reference. If these arrays, objects,
* or any other arrays, or objects that they reference change without a subsequent call to
* the `set` method, their new state will still be serialised.
* Keep in mind that all data here will be serialised every time the park is
* saved, including when the park is periodically saved automatically.
* @param pluginName The name of the plugin to get a store for. If undefined, the
* current plugin's name will be used. Plugin names are case sensitive.
*/
parkStorage: Configuration;
getParkStorage(pluginName?: string): Configuration;
/**
* Render the current state of the map and save to disc.
@ -313,7 +316,7 @@ declare global {
}
interface Configuration {
getAll(namespace: string): { [name: string]: any };
getAll(namespace?: string): { [name: string]: any };
get<T>(key: string): T | undefined;
get<T>(key: string, defaultValue: T): T;
set<T>(key: string, value: T): void;

View File

@ -172,7 +172,7 @@ if (!h) {
All plugins have access to the same shared storage.
If you want to only store data specific to the current park that is loaded, use `context.parkStorage`. Any data stored here will be written to the .park file.
If you want to only store data specific to the current park that is loaded, use `context.getParkStorage`. Any data stored here will be written to the .park file.
> Can plugins communicate with other processes, or the internet?

View File

@ -19,22 +19,30 @@
namespace OpenRCT2::Scripting
{
enum class ScConfigurationKind
{
User,
Shared,
Park
};
class ScConfiguration
{
private:
bool _isUserConfig{};
ScConfigurationKind _kind;
DukValue _backingObject;
public:
// context.configuration
ScConfiguration()
: _isUserConfig(true)
: _kind(ScConfigurationKind::User)
{
}
// context.sharedStorage
ScConfiguration(const DukValue& backingObject)
: _backingObject(backingObject)
// context.sharedStorage / context.getParkStorage
ScConfiguration(ScConfigurationKind kind, const DukValue& backingObject)
: _kind(kind)
, _backingObject(backingObject)
{
}
@ -68,15 +76,18 @@ namespace OpenRCT2::Scripting
std::optional<DukValue> GetNamespaceObject(std::string_view ns) const
{
auto store = _backingObject;
auto k = ns;
bool end;
do
if (!ns.empty())
{
auto [next, remainder] = GetNextNamespace(k);
store = store[next];
k = remainder;
end = store.type() == DukValue::Type::UNDEFINED || remainder.empty();
} while (!end);
auto k = ns;
bool end;
do
{
auto [next, remainder] = GetNextNamespace(k);
store = store[next];
k = remainder;
end = store.type() == DukValue::Type::UNDEFINED || remainder.empty();
} while (!end);
}
return store.type() == DukValue::OBJECT ? std::make_optional(store) : std::nullopt;
}
@ -112,17 +123,26 @@ namespace OpenRCT2::Scripting
bool IsValidNamespace(std::string_view ns) const
{
if (ns.empty() || ns[0] == '.' || ns[ns.size() - 1] == '.')
if (!ns.empty() && (ns[0] == '.' || ns[ns.size() - 1] == '.'))
{
return false;
}
for (size_t i = 1; i < ns.size() - 1; i++)
if (_kind != ScConfigurationKind::Park)
{
if (ns[i - 1] == '.' && ns[i] == '.')
if (ns.empty())
{
return false;
}
for (size_t i = 1; i < ns.size() - 1; i++)
{
if (ns[i - 1] == '.' && ns[i] == '.')
{
return false;
}
}
}
return true;
}
@ -131,13 +151,24 @@ namespace OpenRCT2::Scripting
return !key.empty() && key.find('.') == std::string_view::npos;
}
DukValue getAll(const std::string& ns) const
DukValue getAll(const DukValue& dukNamespace) const
{
DukValue result;
auto ctx = GetContext()->GetScriptEngine().GetContext();
std::string ns = "";
if (dukNamespace.type() == DukValue::Type::STRING)
{
ns = dukNamespace.as_string();
}
else if (dukNamespace.type() != DukValue::Type::UNDEFINED)
{
duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
}
if (IsValidNamespace(ns))
{
if (_isUserConfig)
if (_kind == ScConfigurationKind::User)
{
DukObject obj(ctx);
if (ns == "general")
@ -163,7 +194,7 @@ namespace OpenRCT2::Scripting
DukValue get(const std::string& key, const DukValue& defaultValue) const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
if (_isUserConfig)
if (_kind == ScConfigurationKind::User)
{
if (key == "general.language")
{
@ -214,7 +245,7 @@ namespace OpenRCT2::Scripting
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
if (_isUserConfig)
if (_kind == ScConfigurationKind::User)
{
try
{

View File

@ -55,13 +55,59 @@ namespace OpenRCT2::Scripting
std::shared_ptr<ScConfiguration> sharedStorage_get()
{
auto& scriptEngine = GetContext()->GetScriptEngine();
return std::make_shared<ScConfiguration>(scriptEngine.GetSharedStorage());
return std::make_shared<ScConfiguration>(ScConfigurationKind::Shared, scriptEngine.GetSharedStorage());
}
std::shared_ptr<ScConfiguration> parkStorage_get()
std::shared_ptr<ScConfiguration> GetParkStorageForPlugin(std::string_view pluginName)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
return std::make_shared<ScConfiguration>(scriptEngine.GetParkStorage());
auto parkStore = scriptEngine.GetParkStorage();
auto pluginStore = parkStore[pluginName];
// Create if it doesn't exist
if (pluginStore.type() != DukValue::Type::OBJECT)
{
auto* ctx = scriptEngine.GetContext();
parkStore.push();
duk_push_object(ctx);
duk_put_prop_lstring(ctx, -2, pluginName.data(), pluginName.size());
duk_pop(ctx);
pluginStore = parkStore[pluginName];
}
return std::make_shared<ScConfiguration>(ScConfigurationKind::Park, pluginStore);
}
std::shared_ptr<ScConfiguration> getParkStorage(const DukValue& dukPluginName)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
std::shared_ptr<ScConfiguration> result;
if (dukPluginName.type() == DukValue::Type::STRING)
{
auto& pluginName = dukPluginName.as_string();
if (pluginName.empty())
{
duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin name is empty");
}
result = GetParkStorageForPlugin(pluginName);
}
else if (dukPluginName.type() == DukValue::Type::UNDEFINED)
{
auto plugin = _execInfo.GetCurrentPlugin();
if (plugin == nullptr)
{
duk_error(
scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin name must be specified when used from console.");
}
result = GetParkStorageForPlugin(plugin->GetMetadata().Name);
}
else
{
duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid plugin name.");
}
return result;
}
void captureImage(const DukValue& options)
@ -387,7 +433,7 @@ namespace OpenRCT2::Scripting
dukglue_register_property(ctx, &ScContext::apiVersion_get, nullptr, "apiVersion");
dukglue_register_property(ctx, &ScContext::configuration_get, nullptr, "configuration");
dukglue_register_property(ctx, &ScContext::sharedStorage_get, nullptr, "sharedStorage");
dukglue_register_property(ctx, &ScContext::parkStorage_get, nullptr, "parkStorage");
dukglue_register_method(ctx, &ScContext::getParkStorage, "getParkStorage");
dukglue_register_method(ctx, &ScContext::captureImage, "captureImage");
dukglue_register_method(ctx, &ScContext::getObject, "getObject");
dukglue_register_method(ctx, &ScContext::getAllObjects, "getAllObjects");