2018-03-17 19:31:06 +00:00
|
|
|
/*****************************************************************************
|
|
|
|
|
* Copyright (c) 2014-2018 OpenRCT2 developers
|
|
|
|
|
*
|
|
|
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
|
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
|
|
|
*
|
|
|
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "ScriptEngine.h"
|
2018-07-31 17:59:48 +02:00
|
|
|
|
|
|
|
|
#include "../PlatformEnvironment.h"
|
2018-03-18 16:27:48 +00:00
|
|
|
#include "../core/FileScanner.h"
|
|
|
|
|
#include "../core/Path.hpp"
|
2018-03-17 19:31:06 +00:00
|
|
|
#include "../interface/InteractiveConsole.h"
|
2018-03-18 17:43:47 +00:00
|
|
|
#include "../platform/Platform2.h"
|
2019-07-23 23:21:42 +01:00
|
|
|
#include "Duktape.hpp"
|
2018-03-17 23:26:55 +00:00
|
|
|
#include "ScConsole.hpp"
|
2018-03-18 23:35:58 +00:00
|
|
|
#include "ScContext.hpp"
|
|
|
|
|
#include "ScDisposable.hpp"
|
2018-03-20 19:40:38 +00:00
|
|
|
#include "ScMap.hpp"
|
2019-07-24 21:08:03 +01:00
|
|
|
#include "ScNetwork.hpp"
|
2018-03-18 00:31:02 +00:00
|
|
|
#include "ScPark.hpp"
|
2018-03-20 19:40:38 +00:00
|
|
|
#include "ScThing.hpp"
|
2018-07-31 17:59:48 +02:00
|
|
|
#include "ScTile.hpp"
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <stdexcept>
|
2018-03-17 23:26:55 +00:00
|
|
|
|
2018-03-17 19:31:06 +00:00
|
|
|
using namespace OpenRCT2;
|
|
|
|
|
using namespace OpenRCT2::Scripting;
|
|
|
|
|
|
2018-07-31 17:59:48 +02:00
|
|
|
static std::string Stringify(duk_context* ctx, duk_idx_t idx);
|
2018-03-17 19:31:06 +00:00
|
|
|
|
2018-03-23 23:39:46 +00:00
|
|
|
DukContext::DukContext()
|
|
|
|
|
{
|
|
|
|
|
_context = duk_create_heap_default();
|
|
|
|
|
if (_context == nullptr)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("Unable to initialise duktape context.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DukContext::~DukContext()
|
|
|
|
|
{
|
|
|
|
|
duk_destroy_heap(_context);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 17:59:48 +02:00
|
|
|
ScriptEngine::ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env)
|
|
|
|
|
: _console(console)
|
|
|
|
|
, _env(env)
|
|
|
|
|
, _hookEngine(_execInfo)
|
2018-03-17 19:31:06 +00:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-17 23:26:55 +00:00
|
|
|
void ScriptEngine::Initialise()
|
|
|
|
|
{
|
2018-03-23 23:39:46 +00:00
|
|
|
auto ctx = (duk_context*)_context;
|
2018-03-17 23:26:55 +00:00
|
|
|
ScConsole::Register(ctx);
|
2018-03-18 23:35:58 +00:00
|
|
|
ScContext::Register(ctx);
|
|
|
|
|
ScDisposable::Register(ctx);
|
2018-03-20 19:40:38 +00:00
|
|
|
ScMap::Register(ctx);
|
2019-07-24 21:08:03 +01:00
|
|
|
ScNetwork::Register(ctx);
|
2018-03-18 00:31:02 +00:00
|
|
|
ScPark::Register(ctx);
|
2019-07-24 21:08:03 +01:00
|
|
|
ScPlayer::Register(ctx);
|
2018-03-20 20:28:15 +00:00
|
|
|
ScTile::Register(ctx);
|
|
|
|
|
ScTileElement::Register(ctx);
|
2018-03-20 19:40:38 +00:00
|
|
|
ScThing::Register(ctx);
|
2018-03-17 23:26:55 +00:00
|
|
|
|
2018-03-18 00:31:02 +00:00
|
|
|
dukglue_register_global(ctx, std::make_shared<ScConsole>(_console), "console");
|
2018-03-18 23:35:58 +00:00
|
|
|
dukglue_register_global(ctx, std::make_shared<ScContext>(_execInfo, _hookEngine), "context");
|
2018-03-20 19:40:38 +00:00
|
|
|
dukglue_register_global(ctx, std::make_shared<ScMap>(ctx), "map");
|
2019-07-24 21:08:03 +01:00
|
|
|
dukglue_register_global(ctx, std::make_shared<ScNetwork>(ctx), "network");
|
2018-03-18 00:31:02 +00:00
|
|
|
dukglue_register_global(ctx, std::make_shared<ScPark>(), "park");
|
2018-03-18 16:27:48 +00:00
|
|
|
|
|
|
|
|
LoadPlugins();
|
|
|
|
|
StartPlugins();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptEngine::LoadPlugins()
|
|
|
|
|
{
|
|
|
|
|
auto base = _env.GetDirectoryPath(DIRBASE::USER, DIRID::PLUGIN);
|
|
|
|
|
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());
|
|
|
|
|
try
|
|
|
|
|
{
|
2018-03-23 22:39:12 +00:00
|
|
|
auto plugin = std::make_shared<Plugin>(_context, path);
|
2018-03-23 23:24:36 +00:00
|
|
|
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
|
2018-03-23 22:39:12 +00:00
|
|
|
plugin->Load();
|
|
|
|
|
_plugins.push_back(std::move(plugin));
|
2018-03-18 16:27:48 +00:00
|
|
|
}
|
2018-07-31 17:59:48 +02:00
|
|
|
catch (const std::exception& e)
|
2018-03-18 16:27:48 +00:00
|
|
|
{
|
|
|
|
|
_console.WriteLineError(e.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-31 00:08:59 +01:00
|
|
|
|
2018-05-23 17:56:42 +01:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Enable hot reloading
|
|
|
|
|
_pluginFileWatcher = std::make_unique<FileWatcher>(base);
|
2018-07-31 17:59:48 +02:00
|
|
|
_pluginFileWatcher->OnFileChanged = [this](const std::string& path) {
|
|
|
|
|
std::lock_guard<std::mutex> guard(_changedPluginFilesMutex);
|
|
|
|
|
_changedPluginFiles.push_back(path);
|
|
|
|
|
};
|
2018-05-23 17:56:42 +01:00
|
|
|
}
|
|
|
|
|
catch (const std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
std::printf("Unable to enable hot reloading of plugins: %s\n", e.what());
|
|
|
|
|
}
|
2018-03-18 16:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-18 17:43:47 +00:00
|
|
|
void ScriptEngine::AutoReloadPlugins()
|
|
|
|
|
{
|
2018-03-31 00:08:59 +01:00
|
|
|
if (_changedPluginFiles.size() > 0)
|
2018-03-18 17:43:47 +00:00
|
|
|
{
|
2018-03-31 00:08:59 +01:00
|
|
|
std::lock_guard<std::mutex> guard(_changedPluginFilesMutex);
|
|
|
|
|
for (auto& path : _changedPluginFiles)
|
2018-03-18 17:43:47 +00:00
|
|
|
{
|
2018-07-31 17:59:48 +02:00
|
|
|
auto findResult = std::find_if(_plugins.begin(), _plugins.end(), [&path](const std::shared_ptr<Plugin>& plugin) {
|
|
|
|
|
return Path::Equals(path, plugin->GetPath());
|
|
|
|
|
});
|
2018-03-31 00:08:59 +01:00
|
|
|
if (findResult != _plugins.end())
|
2018-03-18 17:43:47 +00:00
|
|
|
{
|
2018-03-31 00:08:59 +01:00
|
|
|
auto& plugin = *findResult;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_hookEngine.UnsubscribeAll(plugin);
|
2018-03-23 23:24:36 +00:00
|
|
|
|
2018-03-31 00:08:59 +01:00
|
|
|
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
|
|
|
|
|
plugin->Load();
|
|
|
|
|
plugin->Start();
|
|
|
|
|
}
|
2018-07-31 17:59:48 +02:00
|
|
|
catch (const std::exception& e)
|
2018-03-31 00:08:59 +01:00
|
|
|
{
|
|
|
|
|
_console.WriteLineError(e.what());
|
|
|
|
|
}
|
2018-03-18 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
2018-03-31 00:08:59 +01:00
|
|
|
_changedPluginFiles.clear();
|
2018-03-18 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-18 16:27:48 +00:00
|
|
|
void ScriptEngine::StartPlugins()
|
|
|
|
|
{
|
|
|
|
|
for (auto& plugin : _plugins)
|
|
|
|
|
{
|
2018-03-23 23:24:36 +00:00
|
|
|
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
|
2018-03-18 23:35:58 +00:00
|
|
|
try
|
|
|
|
|
{
|
2018-03-23 22:39:12 +00:00
|
|
|
plugin->Start();
|
2018-03-18 23:35:58 +00:00
|
|
|
}
|
2018-07-31 17:59:48 +02:00
|
|
|
catch (const std::exception& e)
|
2018-03-18 23:35:58 +00:00
|
|
|
{
|
|
|
|
|
_console.WriteLineError(e.what());
|
|
|
|
|
}
|
2018-03-18 16:27:48 +00:00
|
|
|
}
|
2018-03-17 23:26:55 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-17 19:31:06 +00:00
|
|
|
void ScriptEngine::Update()
|
|
|
|
|
{
|
2018-03-17 23:26:55 +00:00
|
|
|
if (!_initialised)
|
|
|
|
|
{
|
|
|
|
|
Initialise();
|
|
|
|
|
_initialised = true;
|
|
|
|
|
}
|
2018-03-17 19:31:06 +00:00
|
|
|
while (_evalQueue.size() > 0)
|
|
|
|
|
{
|
|
|
|
|
auto item = std::move(_evalQueue.front());
|
|
|
|
|
_evalQueue.pop();
|
|
|
|
|
auto promise = std::move(std::get<0>(item));
|
|
|
|
|
auto command = std::move(std::get<1>(item));
|
|
|
|
|
if (duk_peval_string(_context, command.c_str()) != 0)
|
|
|
|
|
{
|
|
|
|
|
std::string result = std::string(duk_safe_to_string(_context, -1));
|
|
|
|
|
_console.WriteLineError(result);
|
|
|
|
|
}
|
2019-07-24 21:08:03 +01:00
|
|
|
else if (duk_get_type(_context, -1) != DUK_TYPE_UNDEFINED)
|
2018-03-17 19:31:06 +00:00
|
|
|
{
|
|
|
|
|
std::string result = Stringify(_context, -1);
|
|
|
|
|
_console.WriteLine(result);
|
|
|
|
|
}
|
|
|
|
|
duk_pop(_context);
|
|
|
|
|
// Signal the promise so caller can continue
|
|
|
|
|
promise.set_value();
|
|
|
|
|
}
|
2018-03-18 17:43:47 +00:00
|
|
|
|
|
|
|
|
auto tick = Platform::GetTicks();
|
|
|
|
|
if (tick - _lastHotReloadCheckTick > 1000)
|
|
|
|
|
{
|
|
|
|
|
AutoReloadPlugins();
|
|
|
|
|
_lastHotReloadCheckTick = tick;
|
|
|
|
|
}
|
2018-03-17 19:31:06 +00:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 17:59:48 +02:00
|
|
|
std::future<void> ScriptEngine::Eval(const std::string& s)
|
2018-03-17 19:31:06 +00:00
|
|
|
{
|
|
|
|
|
std::promise<void> barrier;
|
|
|
|
|
auto future = barrier.get_future();
|
|
|
|
|
_evalQueue.emplace(std::move(barrier), s);
|
|
|
|
|
return future;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 17:59:48 +02:00
|
|
|
static std::string Stringify(duk_context* ctx, duk_idx_t idx)
|
2018-03-17 19:31:06 +00:00
|
|
|
{
|
|
|
|
|
auto type = duk_get_type(ctx, idx);
|
|
|
|
|
if (type == DUK_TYPE_OBJECT && !duk_is_function(ctx, idx))
|
|
|
|
|
{
|
|
|
|
|
return duk_json_encode(ctx, idx);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return duk_safe_to_string(ctx, idx);
|
|
|
|
|
}
|
|
|
|
|
}
|