1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-17 20:13:07 +01:00

Start work on intransient plugins

This commit is contained in:
Ted John
2021-03-02 00:17:13 +00:00
parent 91ceecc6fc
commit 6fea0b5025
5 changed files with 235 additions and 27 deletions

View File

@@ -15,7 +15,7 @@
// /// <reference path="/path/to/openrct2.d.ts" />
//
export type PluginType = "local" | "remote";
export type PluginType = "local" | "remote" | intransient;
declare global {
/**

View File

@@ -23,7 +23,7 @@
using namespace OpenRCT2::Scripting;
Plugin::Plugin(duk_context* context, const std::string& path)
Plugin::Plugin(duk_context* context, std::string_view path)
: _context(context)
, _path(path)
{
@@ -73,10 +73,16 @@ void Plugin::Load()
}
_metadata = GetMetadata(DukValue::take_from_stack(_context));
_hasLoaded = true;
}
void Plugin::Start()
{
if (!_hasLoaded)
{
throw std::runtime_error("Plugin has not been loaded.");
}
const auto& mainFunc = _metadata.Main;
if (mainFunc.context() == nullptr)
{
@@ -115,6 +121,12 @@ void Plugin::ThrowIfStopping() const
}
}
void Plugin::Unload()
{
_metadata.Main = {};
_hasLoaded = false;
}
void Plugin::LoadCodeFromFile()
{
_code = File::ReadAllText(_path);
@@ -174,6 +186,8 @@ PluginType Plugin::ParsePluginType(std::string_view type)
return PluginType::Local;
if (type == "remote")
return PluginType::Remote;
if (type == "intransient")
return PluginType::Intransient;
throw std::invalid_argument("Unknown plugin type.");
}
@@ -192,4 +206,9 @@ int32_t Plugin::GetTargetAPIVersion() const
return 33;
}
bool Plugin::IsTransient() const
{
return _metadata.Type != PluginType::Intransient;
}
#endif

View File

@@ -33,6 +33,11 @@ namespace OpenRCT2::Scripting
* modify game state in certain contexts.
*/
Remote,
/**
* Scripts that run when the game starts and never unload.
*/
Intransient,
};
struct PluginMetadata
@@ -53,11 +58,12 @@ namespace OpenRCT2::Scripting
std::string _path;
PluginMetadata _metadata{};
std::string _code;
bool _hasLoaded{};
bool _hasStarted{};
bool _isStopping{};
public:
std::string GetPath() const
std::string_view GetPath() const
{
return _path;
};
@@ -87,10 +93,15 @@ namespace OpenRCT2::Scripting
return _isStopping;
}
bool IsLoaded() const
{
return _hasLoaded;
}
int32_t GetTargetAPIVersion() const;
Plugin() = default;
Plugin(duk_context* context, const std::string& path);
Plugin(duk_context* context, std::string_view path);
Plugin(const Plugin&) = delete;
Plugin(Plugin&&) = delete;
@@ -101,6 +112,9 @@ namespace OpenRCT2::Scripting
void StopEnd();
void ThrowIfStopping() const;
void Unload();
bool IsTransient() const;
private:
void LoadCodeFromFile();

View File

@@ -443,6 +443,149 @@ void ScriptEngine::Initialise()
ClearParkStorage();
}
void ScriptEngine::RefreshPlugins()
{
if (!_initialised)
{
Initialise();
}
// Get a list of removed and added plugin files
auto pluginFiles = GetPluginFiles();
std::vector<std::string> plugins;
std::vector<std::string> removedPlugins;
std::vector<std::string> addedPlugins;
for (const auto& plugin : _plugins)
{
plugins.push_back(std::string(plugin->GetPath()));
}
std::set_difference(
plugins.begin(), plugins.end(), pluginFiles.begin(), pluginFiles.end(), std::back_inserter(removedPlugins));
std::set_difference(
pluginFiles.begin(), pluginFiles.end(), plugins.begin(), plugins.end(), std::back_inserter(addedPlugins));
// Unregister plugin files that were removed
for (const auto& plugin : removedPlugins)
{
UnregisterPlugin(plugin);
}
// Register plugin files that were added
for (const auto& plugin : addedPlugins)
{
RegisterPlugin(plugin);
}
// Turn on hot reload if not already enabled
if (!_hotReloading && gConfigPlugin.enable_hot_reloading && network_get_mode() == NETWORK_MODE_NONE)
{
SetupHotReloading();
}
// Start any new intransient plugins
StartIntransientPlugins();
}
std::vector<std::string> ScriptEngine::GetPluginFiles() const
{
// Scan for .js files in plugin directory
std::vector<std::string> pluginFiles;
auto base = _env.GetDirectoryPath(DIRBASE::USER, DIRID::PLUGIN);
if (Path::DirectoryExists(base))
{
auto pattern = Path::Combine(base, "*.js");
auto scanner = std::unique_ptr<IFileScanner>(Path::ScanDirectory(pattern, true));
while (scanner->Next())
{
auto path = std::string(scanner->GetPath());
if (ShouldLoadScript(path))
{
pluginFiles.push_back(path);
}
}
}
return pluginFiles;
}
bool ScriptEngine::ShouldLoadScript(std::string_view path)
{
// A lot of JavaScript is often found in a node_modules directory tree and is most likely unwanted, so ignore it
return path.find("/node_modules/") == std::string_view::npos && path.find("\\node_modules\\") == std::string_view::npos;
}
void ScriptEngine::UnregisterPlugin(std::string_view path)
{
try
{
auto pluginIt = std::find_if(_plugins.begin(), _plugins.end(), [path](const std::shared_ptr<Plugin>& plugin) {
return plugin->GetPath() == path;
});
auto& plugin = *pluginIt;
StopPlugin(plugin);
UnloadPlugin(plugin);
LogPluginInfo(plugin, "Unregistered");
_plugins.erase(pluginIt);
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
void ScriptEngine::RegisterPlugin(std::string_view path)
{
try
{
auto plugin = std::make_shared<Plugin>(_context, path);
// We must load the plugin to get the metadata for it
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, false);
plugin->Load();
// Unload the plugin now, metadata is kept
plugin->Unload();
LogPluginInfo(plugin, "Registered");
_plugins.push_back(std::move(plugin));
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
void ScriptEngine::StartIntransientPlugins()
{
for (auto& plugin : _plugins)
{
if (!plugin->HasStarted() && !plugin->IsTransient())
{
LoadPlugin(plugin);
StartPlugin(plugin);
}
}
}
void ScriptEngine::StopUnloadRegisterAllPlugins()
{
std::vector<std::string> pluginPaths;
for (auto& plugin : _plugins)
{
pluginPaths.push_back(std::string(plugin->GetPath()));
StopPlugin(plugin);
}
for (auto& plugin : _plugins)
{
UnloadPlugin(plugin);
}
for (auto& pluginPath : pluginPaths)
{
UnregisterPlugin(pluginPath);
}
}
void ScriptEngine::LoadPlugins()
{
if (!_initialised)
@@ -487,18 +630,19 @@ void ScriptEngine::LoadPlugin(std::shared_ptr<Plugin>& plugin)
{
try
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, false);
plugin->Load();
auto metadata = plugin->GetMetadata();
if (metadata.MinApiVersion <= OPENRCT2_PLUGIN_API_VERSION)
if (!plugin->IsLoaded())
{
LogPluginInfo(plugin, "Loaded");
_plugins.push_back(std::move(plugin));
}
else
{
LogPluginInfo(plugin, "Requires newer API version: v" + std::to_string(metadata.MinApiVersion));
const auto& metadata = plugin->GetMetadata();
if (metadata.MinApiVersion <= OPENRCT2_PLUGIN_API_VERSION)
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, false);
plugin->Load();
LogPluginInfo(plugin, "Loaded");
}
else
{
LogPluginInfo(plugin, "Requires newer API version: v" + std::to_string(metadata.MinApiVersion));
}
}
}
catch (const std::exception& e)
@@ -507,6 +651,29 @@ void ScriptEngine::LoadPlugin(std::shared_ptr<Plugin>& plugin)
}
}
void ScriptEngine::UnloadPlugin(std::shared_ptr<Plugin>& plugin)
{
plugin->Unload();
LogPluginInfo(plugin, "Unloaded");
}
void ScriptEngine::StartPlugin(std::shared_ptr<Plugin> plugin)
{
if (!plugin->HasStarted() && ShouldStartPlugin(plugin))
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, false);
try
{
LogPluginInfo(plugin, "Started");
plugin->Start();
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
}
void ScriptEngine::StopPlugin(std::shared_ptr<Plugin> plugin)
{
if (plugin->HasStarted())
@@ -526,22 +693,20 @@ void ScriptEngine::StopPlugin(std::shared_ptr<Plugin> plugin)
}
}
bool ScriptEngine::ShouldLoadScript(const std::string& path)
{
// A lot of JavaScript is often found in a node_modules directory tree and is most likely unwanted, so ignore it
return path.find("/node_modules/") == std::string::npos && path.find("\\node_modules\\") == std::string::npos;
}
void ScriptEngine::SetupHotReloading()
{
try
{
auto base = _env.GetDirectoryPath(DIRBASE::USER, DIRID::PLUGIN);
_pluginFileWatcher = std::make_unique<FileWatcher>(base);
_pluginFileWatcher->OnFileChanged = [this](const std::string& path) {
std::lock_guard<std::mutex> guard(_changedPluginFilesMutex);
_changedPluginFiles.emplace(path);
};
if (Path::DirectoryExists(base))
{
_pluginFileWatcher = std::make_unique<FileWatcher>(base);
_pluginFileWatcher->OnFileChanged = [this](const std::string& path) {
std::lock_guard<std::mutex> guard(_changedPluginFilesMutex);
_changedPluginFiles.emplace(path);
};
_hotReloading = true;
}
}
catch (const std::exception& e)
{
@@ -652,6 +817,7 @@ void ScriptEngine::Tick()
if (!_initialised)
{
Initialise();
RefreshPlugins();
}
if (_pluginsLoaded)

View File

@@ -145,6 +145,7 @@ namespace OpenRCT2::Scripting
IPlatformEnvironment& _env;
DukContext _context;
bool _initialised{};
bool _hotReloading{};
bool _pluginsLoaded{};
bool _pluginsStarted{};
std::queue<std::tuple<std::promise<void>, std::string>> _evalQueue;
@@ -247,12 +248,20 @@ namespace OpenRCT2::Scripting
private:
void Initialise();
void RefreshPlugins();
std::vector<std::string> GetPluginFiles() const;
void UnregisterPlugin(std::string_view path);
void RegisterPlugin(std::string_view path);
void StartIntransientPlugins();
void StartPlugins();
void StopPlugins();
void LoadPlugin(const std::string& path);
void LoadPlugin(std::shared_ptr<Plugin>& plugin);
void UnloadPlugin(std::shared_ptr<Plugin>& plugin);
void StartPlugin(std::shared_ptr<Plugin> plugin);
void StopPlugin(std::shared_ptr<Plugin> plugin);
bool ShouldLoadScript(const std::string& path);
void StopUnloadRegisterAllPlugins();
static bool ShouldLoadScript(std::string_view path);
bool ShouldStartPlugin(const std::shared_ptr<Plugin>& plugin);
void SetupHotReloading();
void AutoReloadPlugins();