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:
11
distribution/openrct2.d.ts
vendored
11
distribution/openrct2.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user