1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-06 06:32:56 +01:00

Add hook system

This commit is contained in:
Ted John
2018-03-18 23:35:58 +00:00
parent 3556dead74
commit 4a575975f6
12 changed files with 329 additions and 3 deletions

View File

@@ -36,7 +36,11 @@ export interface Context {
/**
* Subscribes to the given hook.
*/
subscribe: (hook: string, callback: Function) => void;
subscribe(hook: string, callback: Function): IDisposable;
}
export interface IDisposable {
dispose(): void;
}
export interface Ride {

View File

@@ -57,6 +57,7 @@
#include "ride/TrackDesignRepository.h"
#include "scenario/Scenario.h"
#include "scenario/ScenarioRepository.h"
#include "scripting/HookEngine.h"
#include "scripting/ScriptEngine.h"
#include "title/TitleScreen.h"
#include "title/TitleSequenceManager.h"

View File

@@ -26,6 +26,7 @@
#include "peep/Staff.h"
#include "platform/Platform2.h"
#include "scenario/Scenario.h"
#include "scripting/ScriptEngine.h"
#include "title/TitleScreen.h"
#include "title/TitleSequencePlayer.h"
#include "ui/UiContext.h"
@@ -38,6 +39,7 @@
#include <algorithm>
using namespace OpenRCT2;
using namespace OpenRCT2::Scripting;
GameState::GameState()
{
@@ -267,6 +269,9 @@ void GameState::UpdateLogic()
}
}
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
hookEngine.Call(HOOK_TYPE::INTERVAL_TICK);
date_update();
_date = Date(gDateMonthTicks, gDateMonthTicks);

View File

@@ -0,0 +1,99 @@
/*****************************************************************************
* 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 <unordered_map>
#include "HookEngine.h"
#include "ScriptEngine.h"
using namespace OpenRCT2::Scripting;
HOOK_TYPE OpenRCT2::Scripting::GetHookType(const std::string &name)
{
static const std::unordered_map<std::string, HOOK_TYPE> LookupTable({
{ "interval.tick", HOOK_TYPE::INTERVAL_TICK },
{ "interval.day", HOOK_TYPE::INTERVAL_DAY },
});
auto result = LookupTable.find(name);
return (result != LookupTable.end()) ?
result->second :
HOOK_TYPE::UNDEFINED;
}
HookEngine::HookEngine(ScriptExecutionInfo& execInfo) :
_execInfo(execInfo)
{
_hookMap.resize(NUM_HOOK_TYPES);
for (size_t i = 0; i < NUM_HOOK_TYPES; i++)
{
_hookMap[i].Type = static_cast<HOOK_TYPE>(i);
}
}
uint32 HookEngine::Subscribe(HOOK_TYPE type, Plugin& owner, const DukValue &function)
{
auto& hookList = GetHookList(type);
auto cookie = _nextCookie++;
Hook hook(cookie, owner, function);
hookList.Hooks.push_back(hook);
return cookie;
}
void HookEngine::Unsubscribe(HOOK_TYPE type, uint32 cookie)
{
auto& hookList = GetHookList(type);
auto& hooks = hookList.Hooks;
for (auto it = hooks.begin(); it != hooks.end(); it++)
{
if (it->Cookie == cookie)
{
hooks.erase(it);
break;
}
}
}
void HookEngine::UnsubscribeAll(const Plugin& owner)
{
for (auto& hookList : _hookMap)
{
auto& hooks = hookList.Hooks;
for (auto it = hooks.begin(); it != hooks.end();)
{
if (it->Owner == &owner)
{
it = hooks.erase(it);
}
else
{
it++;
}
}
}
}
void HookEngine::Call(HOOK_TYPE type)
{
auto& hookList = GetHookList(type);
for (auto& hook : hookList.Hooks)
{
_execInfo.SetCurrentPlugin(hook.Owner);
const auto& function = hook.Function;
function.push();
duk_pcall(function.context(), 0);
duk_pop(function.context());
}
_execInfo.SetCurrentPlugin(nullptr);
}
HookList& HookEngine::GetHookList(HOOK_TYPE type)
{
auto index = static_cast<size_t>(type);
return _hookMap[index];
}

View File

@@ -0,0 +1,81 @@
/*****************************************************************************
* 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.
*****************************************************************************/
#pragma once
#include "../common.h"
#include <memory>
#include <string>
#include <vector>
#include <dukglue/dukglue.h>
namespace OpenRCT2::Scripting
{
class ScriptExecutionInfo;
class Plugin;
enum class HOOK_TYPE
{
INTERVAL_TICK,
INTERVAL_DAY,
COUNT,
UNDEFINED = -1,
};
constexpr size_t NUM_HOOK_TYPES = static_cast<size_t>(HOOK_TYPE::COUNT);
HOOK_TYPE GetHookType(const std::string &name);
struct Hook
{
uint32 Cookie;
Plugin * Owner;
DukValue Function;
Hook();
Hook(uint32 cookie, Plugin& owner, const DukValue &function)
: Cookie(cookie),
Owner(&owner),
Function(function)
{
}
};
struct HookList
{
HOOK_TYPE Type;
std::vector<Hook> Hooks;
HookList() {}
HookList(const HookList&) = delete;
HookList(HookList&& src)
: Type(std::move(src.Type)),
Hooks(std::move(src.Hooks))
{
}
};
class HookEngine
{
private:
ScriptExecutionInfo& _execInfo;
std::vector<HookList> _hookMap;
size_t _numHooks{};
uint32_t _nextCookie = 1;
public:
HookEngine(ScriptExecutionInfo& execInfo);
HookEngine(const HookEngine&) = delete;
uint32 Subscribe(HOOK_TYPE type, Plugin& owner, const DukValue &function);
void Unsubscribe(HOOK_TYPE type, uint32 cookie);
void UnsubscribeAll(const Plugin& owner);
void Call(HOOK_TYPE type);
private:
HookList& GetHookList(HOOK_TYPE type);
};
}

View File

@@ -1,3 +1,5 @@
#pragma once
#include <dukglue/dukglue.h>
#include "../interface/Console.h"

View File

@@ -0,0 +1,57 @@
#pragma once
#include <dukglue/dukglue.h>
#include "HookEngine.h"
#include "ScDisposable.hpp"
#include "ScriptEngine.h"
#include <cstdio>
#include <memory>
namespace OpenRCT2::Scripting
{
class ScContext
{
private:
ScriptExecutionInfo& _execInfo;
HookEngine& _hookEngine;
public:
ScContext(ScriptExecutionInfo& execInfo, HookEngine& hookEngine)
: _execInfo(execInfo),
_hookEngine(hookEngine)
{
}
std::shared_ptr<ScDisposable> subscribe(const std::string &hook, const DukValue &callback)
{
auto hookType = GetHookType(hook);
if (hookType == HOOK_TYPE::UNDEFINED)
{
throw DukException() << "Unknown hook type: " << hook;
}
if (!callback.is_function())
{
throw DukException() << "Expected function for callback";
}
auto owner = _execInfo.GetCurrentPlugin();
if (owner == nullptr)
{
throw DukException() << "Not in a plugin context";
}
auto cookie = _hookEngine.Subscribe(hookType, *owner, callback);
return std::make_shared<ScDisposable>(
[this, hookType, cookie]()
{
_hookEngine.Unsubscribe(hookType, cookie);
});
}
static void Register(duk_context * ctx)
{
dukglue_register_method(ctx, &ScContext::subscribe, "subscribe");
}
};
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <dukglue/dukglue.h>
#include <functional>
namespace OpenRCT2::Scripting
{
class ScDisposable
{
private:
std::function<void()> _onDispose;
public:
ScDisposable(std::function<void()> onDispose) :
_onDispose(onDispose)
{
}
void dispose()
{
if (_onDispose)
{
_onDispose();
}
}
static void Register(duk_context * ctx)
{
dukglue_register_method(ctx, &ScDisposable::dispose, "dispose");
}
};
}

View File

@@ -1,3 +1,5 @@
#pragma once
#include <algorithm>
#include <dukglue/dukglue.h>
#include "../common.h"

View File

@@ -19,6 +19,8 @@
#include <stdexcept>
#include "ScConsole.hpp"
#include "ScContext.hpp"
#include "ScDisposable.hpp"
#include "ScPark.hpp"
using namespace OpenRCT2;
@@ -28,7 +30,8 @@ static std::string Stringify(duk_context * ctx, duk_idx_t idx);
ScriptEngine::ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env) :
_console(console),
_env(env)
_env(env),
_hookEngine(_execInfo)
{
}
@@ -47,9 +50,12 @@ void ScriptEngine::Initialise()
auto ctx = _context;
ScConsole::Register(ctx);
ScContext::Register(ctx);
ScDisposable::Register(ctx);
ScPark::Register(ctx);
dukglue_register_global(ctx, std::make_shared<ScConsole>(_console), "console");
dukglue_register_global(ctx, std::make_shared<ScContext>(_execInfo, _hookEngine), "context");
dukglue_register_global(ctx, std::make_shared<ScPark>(), "park");
LoadPlugins();
@@ -67,6 +73,7 @@ void ScriptEngine::LoadPlugins()
try
{
Plugin p(_context, path);
_execInfo.SetCurrentPlugin(&p);
p.Load();
p.EnableHotReload();
_plugins.push_back(std::move(p));
@@ -76,6 +83,7 @@ void ScriptEngine::LoadPlugins()
_console.WriteLineError(e.what());
}
}
_execInfo.SetCurrentPlugin(nullptr);
}
void ScriptEngine::AutoReloadPlugins()
@@ -86,6 +94,8 @@ void ScriptEngine::AutoReloadPlugins()
{
try
{
_hookEngine.UnsubscribeAll(plugin);
_execInfo.SetCurrentPlugin(&plugin);
plugin.Load();
plugin.Start();
}
@@ -95,14 +105,24 @@ void ScriptEngine::AutoReloadPlugins()
}
}
}
_execInfo.SetCurrentPlugin(nullptr);
}
void ScriptEngine::StartPlugins()
{
for (auto& plugin : _plugins)
{
plugin.Start();
_execInfo.SetCurrentPlugin(&plugin);
try
{
plugin.Start();
}
catch (const std::exception &e)
{
_console.WriteLineError(e.what());
}
}
_execInfo.SetCurrentPlugin(nullptr);
}
void ScriptEngine::Update()

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "HookEngine.h"
#include "Plugin.h"
#include <future>
#include <queue>
@@ -27,6 +28,16 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
class ScriptExecutionInfo
{
private:
Plugin * _plugin;
public:
Plugin * GetCurrentPlugin() { return _plugin; }
void SetCurrentPlugin(Plugin * value) { _plugin = value; }
};
class ScriptEngine
{
private:
@@ -37,12 +48,16 @@ namespace OpenRCT2::Scripting
std::queue<std::tuple<std::promise<void>, std::string>> _evalQueue;
std::vector<Plugin> _plugins;
uint32 _lastHotReloadCheckTick{};
HookEngine _hookEngine;
ScriptExecutionInfo _execInfo;
public:
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
ScriptEngine(ScriptEngine&) = delete;
~ScriptEngine();
HookEngine& GetHookEngine() { return _hookEngine; }
void Update();
std::future<void> Eval(const std::string &s);

View File

@@ -625,4 +625,12 @@ public:
duk_pop(mContext);
return result;
}
bool is_function() const
{
push();
bool result = duk_is_function(mContext, -1);
duk_pop(mContext);
return result;
}
};