1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-04 13:42:55 +01:00

Do not load plugins on title screen

This commit is contained in:
Ted John
2020-02-06 23:15:20 +00:00
parent 2e62078c30
commit e6915e8799
8 changed files with 205 additions and 24 deletions

View File

@@ -15,6 +15,17 @@
// /// <reference path="/path/to/openrct2.d.ts" />
//
export type PluginType = "server" | "client" | "server_client";
export interface PluginMetadata {
name: string;
version: string;
authors: string | string[];
type: PluginType;
minApiVersion: number;
main: () => void;
}
export interface Console {
clear(): void;
log(message?: any, ...optionalParams: any[]): void;

View File

@@ -46,6 +46,7 @@
#include "ride/TrackDesign.h"
#include "ride/Vehicle.h"
#include "scenario/Scenario.h"
#include "scripting/ScriptEngine.h"
#include "title/TitleScreen.h"
#include "ui/UiContext.h"
#include "ui/WindowManager.h"
@@ -587,6 +588,13 @@ void game_load_init()
audio_stop_title_music();
gGameSpeed = 1;
GetContext()->GetScriptEngine().LoadPlugins();
}
void game_finish()
{
GetContext()->GetScriptEngine().UnloadPlugins();
}
/**
@@ -801,6 +809,7 @@ static void game_load_or_quit_no_save_prompt_callback(int32_t result, const utf8
{
if (result == MODAL_RESULT_OK)
{
game_finish();
window_close_by_class(WC_EDITOR_OBJECT_SELECTION);
context_load_park_from_file(path);
}
@@ -843,10 +852,12 @@ void game_load_or_quit_no_save_prompt()
}
gGameSpeed = 1;
gFirstTimeSaving = true;
game_finish();
title_load();
break;
}
default:
game_finish();
openrct2_finish();
break;
}

View File

@@ -78,6 +78,15 @@ void HookEngine::UnsubscribeAll(std::shared_ptr<const Plugin> owner)
}
}
void HookEngine::UnsubscribeAll()
{
for (auto& hookList : _hookMap)
{
auto& hooks = hookList.Hooks;
hooks.clear();
}
}
void HookEngine::Call(HOOK_TYPE type)
{
auto& hookList = GetHookList(type);

View File

@@ -79,6 +79,7 @@ namespace OpenRCT2::Scripting
uint32_t Subscribe(HOOK_TYPE type, std::shared_ptr<Plugin> owner, const DukValue& function);
void Unsubscribe(HOOK_TYPE type, uint32_t cookie);
void UnsubscribeAll(std::shared_ptr<const Plugin> owner);
void UnsubscribeAll();
void Call(HOOK_TYPE type);
void Call(HOOK_TYPE type, const std::initializer_list<std::pair<std::string_view, std::any>>& args);

View File

@@ -74,19 +74,40 @@ void Plugin::Start()
throw std::runtime_error("[" + _metadata.Name + "] " + val);
}
duk_pop(_context);
_hasStarted = true;
}
void Plugin::Stop()
{
_hasStarted = false;
}
void Plugin::Update()
{
}
static std::string TryGetString(const DukValue& value, const std::string& message)
{
if (value.type() != DukValue::Type::STRING)
throw std::runtime_error(message);
return value.as_string();
}
PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata)
{
PluginMetadata metadata;
if (dukMetadata.type() == DukValue::Type::OBJECT)
{
metadata.Name = dukMetadata["name"].as_string();
metadata.Version = dukMetadata["version"].as_string();
metadata.Name = TryGetString(dukMetadata["name"], "Plugin name not specified.");
metadata.Version = TryGetString(dukMetadata["version"], "Plugin version not specified.");
metadata.Type = ParsePluginType(TryGetString(dukMetadata["type"], "Plugin type not specified."));
auto dukMinApiVersion = dukMetadata["minApiVersion"];
if (dukMinApiVersion.type() == DukValue::Type::NUMBER)
{
metadata.MinApiVersion = dukMinApiVersion.as_int();
}
auto dukAuthors = dukMetadata["authors"];
dukAuthors.push();
@@ -97,7 +118,7 @@ PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata)
return v.as_string();
});
}
else
else if (dukAuthors.type() == DukValue::Type::STRING)
{
metadata.Authors = { dukAuthors.as_string() };
}
@@ -105,3 +126,14 @@ PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata)
}
return metadata;
}
PluginType Plugin::ParsePluginType(const std::string_view& type)
{
if (type == "server")
return PluginType::Server;
if (type == "client")
return PluginType::Server;
if (type == "server_client")
return PluginType::Server;
throw std::invalid_argument("Unknown plugin type.");
}

View File

@@ -13,24 +13,48 @@
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace OpenRCT2::Scripting
{
enum class PluginType
{
/**
* Scripts that can run on any client with no impact on the game state.
*/
Client,
/**
* Scripts that can run on servers with no impact on the game state and will not
* be uploaded to clients.
*/
Server,
/**
* Scripts that can run on servers and will be uploaded to clients with ability to
* modify game state in certain contexts.
*/
ServerClient,
};
struct PluginMetadata
{
std::string Name;
std::string Version;
std::vector<std::string> Authors;
PluginType Type;
int32_t MinApiVersion{};
DukValue Main;
};
class Plugin
{
private:
duk_context* _context;
duk_context* _context{};
std::string _path;
PluginMetadata _metadata;
bool _hasStarted{};
public:
std::string GetPath() const
@@ -38,6 +62,16 @@ namespace OpenRCT2::Scripting
return _path;
};
const PluginMetadata& GetMetadata() const
{
return _metadata;
}
bool HasStarted() const
{
return _hasStarted;
}
Plugin()
{
}
@@ -47,9 +81,11 @@ namespace OpenRCT2::Scripting
void Load();
void Start();
void Stop();
void Update();
private:
static PluginMetadata GetMetadata(const DukValue& dukMetadata);
static PluginType ParsePluginType(const std::string_view& type);
};
} // namespace OpenRCT2::Scripting

View File

@@ -32,6 +32,8 @@ using namespace OpenRCT2::Scripting;
static std::string Stringify(duk_context* ctx, duk_idx_t idx);
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 1;
DukContext::DukContext()
{
_context = duk_create_heap_default();
@@ -74,8 +76,9 @@ void ScriptEngine::Initialise()
dukglue_register_global(ctx, std::make_shared<ScNetwork>(ctx), "network");
dukglue_register_global(ctx, std::make_shared<ScPark>(), "park");
LoadPlugins();
StartPlugins();
_initialised = true;
_pluginsLoaded = false;
_pluginsStarted = false;
}
void ScriptEngine::LoadPlugins()
@@ -93,7 +96,17 @@ void ScriptEngine::LoadPlugins()
auto plugin = std::make_shared<Plugin>(_context, path);
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
plugin->Load();
_plugins.push_back(std::move(plugin));
auto metadata = plugin->GetMetadata();
if (metadata.MinApiVersion <= OPENRCT2_PLUGIN_API_VERSION)
{
LogPluginInfo(plugin, "Loaded");
_plugins.push_back(std::move(plugin));
}
else
{
LogPluginInfo(plugin, "Requires newer API version: v" + std::to_string(metadata.MinApiVersion));
}
}
catch (const std::exception& e)
{
@@ -115,6 +128,8 @@ void ScriptEngine::LoadPlugins()
{
std::printf("Unable to enable hot reloading of plugins: %s\n", e.what());
}
_pluginsLoaded = true;
}
bool ScriptEngine::ShouldLoadScript(const std::string& path)
@@ -142,6 +157,7 @@ void ScriptEngine::AutoReloadPlugins()
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
plugin->Load();
LogPluginInfo(plugin, "Reloaded");
plugin->Start();
}
catch (const std::exception& e)
@@ -154,20 +170,57 @@ void ScriptEngine::AutoReloadPlugins()
}
}
void ScriptEngine::UnloadPlugins()
{
StopPlugins();
for (auto& plugin : _plugins)
{
LogPluginInfo(plugin, "Unloaded");
}
_plugins.clear();
_pluginsLoaded = false;
_pluginsStarted = false;
}
void ScriptEngine::StartPlugins()
{
for (auto& plugin : _plugins)
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
try
if (!plugin->HasStarted())
{
plugin->Start();
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
try
{
plugin->Start();
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
}
_pluginsStarted = true;
}
void ScriptEngine::StopPlugins()
{
_hookEngine.UnsubscribeAll();
for (auto& plugin : _plugins)
{
if (plugin->HasStarted())
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
try
{
plugin->Stop();
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
}
_pluginsStarted = false;
}
void ScriptEngine::Update()
@@ -175,8 +228,30 @@ void ScriptEngine::Update()
if (!_initialised)
{
Initialise();
_initialised = true;
}
if (_pluginsLoaded)
{
if (!_pluginsStarted)
{
StartPlugins();
}
else
{
auto tick = Platform::GetTicks();
if (tick - _lastHotReloadCheckTick > 1000)
{
AutoReloadPlugins();
_lastHotReloadCheckTick = tick;
}
}
}
ProcessREPL();
}
void ScriptEngine::ProcessREPL()
{
while (_evalQueue.size() > 0)
{
auto item = std::move(_evalQueue.front());
@@ -197,13 +272,6 @@ void ScriptEngine::Update()
// Signal the promise so caller can continue
promise.set_value();
}
auto tick = Platform::GetTicks();
if (tick - _lastHotReloadCheckTick > 1000)
{
AutoReloadPlugins();
_lastHotReloadCheckTick = tick;
}
}
std::future<void> ScriptEngine::Eval(const std::string& s)
@@ -214,6 +282,12 @@ std::future<void> ScriptEngine::Eval(const std::string& s)
return future;
}
void ScriptEngine::LogPluginInfo(const std::shared_ptr<Plugin>& plugin, const std::string_view& message)
{
const auto& pluginName = plugin->GetMetadata().Name;
_console.WriteLine("[" + pluginName + "] " + std::string(message));
}
static std::string Stringify(duk_context* ctx, duk_idx_t idx)
{
auto type = duk_get_type(ctx, idx);

View File

@@ -74,7 +74,7 @@ namespace OpenRCT2::Scripting
public:
DukContext();
DukContext(DukContext&) = delete;
DukContext(DukContext&& src)
DukContext(DukContext&& src) noexcept
: _context(std::move(src._context))
{
}
@@ -93,6 +93,8 @@ namespace OpenRCT2::Scripting
IPlatformEnvironment& _env;
DukContext _context;
bool _initialised{};
bool _pluginsLoaded{};
bool _pluginsStarted{};
std::queue<std::tuple<std::promise<void>, std::string>> _evalQueue;
std::vector<std::shared_ptr<Plugin>> _plugins;
uint32_t _lastHotReloadCheckTick{};
@@ -120,14 +122,19 @@ namespace OpenRCT2::Scripting
return _execInfo;
}
void LoadPlugins();
void UnloadPlugins();
void Update();
std::future<void> Eval(const std::string& s);
void LogPluginInfo(const std::shared_ptr<Plugin>& plugin, const std::string_view& message);
private:
void Initialise();
void LoadPlugins();
void StartPlugins();
void StopPlugins();
bool ShouldLoadScript(const std::string& path);
void AutoReloadPlugins();
void ProcessREPL();
};
} // namespace OpenRCT2::Scripting