mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-06 06:32:56 +01:00
Add hook system
This commit is contained in:
6
distribution/openrct2.d.ts
vendored
6
distribution/openrct2.d.ts
vendored
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
99
src/openrct2/scripting/HookEngine.cpp
Normal file
99
src/openrct2/scripting/HookEngine.cpp
Normal 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];
|
||||
}
|
||||
81
src/openrct2/scripting/HookEngine.h
Normal file
81
src/openrct2/scripting/HookEngine.h
Normal 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);
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <dukglue/dukglue.h>
|
||||
#include "../interface/Console.h"
|
||||
|
||||
|
||||
57
src/openrct2/scripting/ScContext.hpp
Normal file
57
src/openrct2/scripting/ScContext.hpp
Normal 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");
|
||||
}
|
||||
};
|
||||
}
|
||||
32
src/openrct2/scripting/ScDisposable.hpp
Normal file
32
src/openrct2/scripting/ScDisposable.hpp
Normal 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");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <dukglue/dukglue.h>
|
||||
#include "../common.h"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
8
src/openrct2/thirdparty/dukglue/dukvalue.h
vendored
8
src/openrct2/thirdparty/dukglue/dukvalue.h
vendored
@@ -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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user