mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-02-01 11:15:13 +01:00
[Plugin] Add context.setInterval and context.setTimeout
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
- Feature: [#13583] [Plugin] Add allowed_hosts to plugin section of config.
|
||||
- Feature: [#13593] [Plugin] Add ability to read and change the position of ride vehicles.
|
||||
- Feature: [#13614] Add terrain surfaces from RollerCoaster Tycoon 1.
|
||||
- Feature: [#13675] [Plugin] Add context.setInterval and context.setTimeout.
|
||||
- Change: [#13346] [Plugin] Renamed FootpathScenery to FootpathAddition, fix typos.
|
||||
- Fix: [#12895] Mechanics are called to repair rides that have already been fixed.
|
||||
- Fix: [#13102] Underflow on height chart (Ride measurements).
|
||||
|
||||
28
distribution/openrct2.d.ts
vendored
28
distribution/openrct2.d.ts
vendored
@@ -237,6 +237,34 @@ declare global {
|
||||
subscribe(hook: "network.leave", callback: (e: NetworkEventArgs) => void): IDisposable;
|
||||
subscribe(hook: "ride.ratings.calculate", callback: (e: RideRatingsCalculateArgs) => void): IDisposable;
|
||||
subscribe(hook: "action.location", callback: (e: ActionLocationArgs) => void): IDisposable;
|
||||
|
||||
/**
|
||||
* Registers a function to be called every so often in realtime, specified by the given delay.
|
||||
* @param callback The function to call every time the delay has elapsed.
|
||||
* @param delay The number of milliseconds to wait between each call to the given function.
|
||||
*/
|
||||
setInterval(callback: Function, delay: number): number;
|
||||
|
||||
/**
|
||||
* Like `setInterval`, except the callback will only execute once after the given delay.
|
||||
* @param callback The function to call after the given delay has elapsed.
|
||||
* @param delay The number of milliseconds to wait for before calling the given function.
|
||||
*/
|
||||
setTimeout(callback: Function, delay: number): number;
|
||||
|
||||
/**
|
||||
* Removes the registered interval specified by the numeric handle. The handles
|
||||
* are shared with `setTimeout`.
|
||||
* @param handle
|
||||
*/
|
||||
clearInterval(handle: number): void;
|
||||
|
||||
/**
|
||||
* Removes the registered timeout specified by the numeric handle. The handles
|
||||
* are shared with `setInterval`.
|
||||
* @param handle The numerical handle of the registered timeout to remove.
|
||||
*/
|
||||
clearTimeout(handle: number): void;
|
||||
}
|
||||
|
||||
interface Configuration {
|
||||
|
||||
@@ -324,6 +324,51 @@ namespace OpenRCT2::Scripting
|
||||
}
|
||||
}
|
||||
|
||||
int32_t SetIntervalOrTimeout(DukValue callback, int32_t delay, bool repeat)
|
||||
{
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
auto ctx = scriptEngine.GetContext();
|
||||
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
|
||||
|
||||
int32_t handle = 0;
|
||||
if (callback.is_function())
|
||||
{
|
||||
handle = scriptEngine.AddInterval(plugin, delay, repeat, std::move(callback));
|
||||
}
|
||||
else
|
||||
{
|
||||
duk_error(ctx, DUK_ERR_ERROR, "callback was not a function.");
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ClearIntervalOrTimeout(int32_t handle)
|
||||
{
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
|
||||
scriptEngine.RemoveInterval(plugin, handle);
|
||||
}
|
||||
|
||||
int32_t setInterval(DukValue callback, int32_t delay)
|
||||
{
|
||||
return SetIntervalOrTimeout(callback, delay, true);
|
||||
}
|
||||
|
||||
int32_t setTimeout(DukValue callback, int32_t delay)
|
||||
{
|
||||
return SetIntervalOrTimeout(callback, delay, false);
|
||||
}
|
||||
|
||||
void clearInterval(int32_t handle)
|
||||
{
|
||||
ClearIntervalOrTimeout(handle);
|
||||
}
|
||||
|
||||
void clearTimeout(int32_t handle)
|
||||
{
|
||||
ClearIntervalOrTimeout(handle);
|
||||
}
|
||||
|
||||
public:
|
||||
static void Register(duk_context* ctx)
|
||||
{
|
||||
@@ -338,6 +383,10 @@ namespace OpenRCT2::Scripting
|
||||
dukglue_register_method(ctx, &ScContext::queryAction, "queryAction");
|
||||
dukglue_register_method(ctx, &ScContext::executeAction, "executeAction");
|
||||
dukglue_register_method(ctx, &ScContext::registerAction, "registerAction");
|
||||
dukglue_register_method(ctx, &ScContext::setInterval, "setInterval");
|
||||
dukglue_register_method(ctx, &ScContext::setTimeout, "setTimeout");
|
||||
dukglue_register_method(ctx, &ScContext::clearInterval, "clearInterval");
|
||||
dukglue_register_method(ctx, &ScContext::clearTimeout, "clearTimeout");
|
||||
}
|
||||
};
|
||||
} // namespace OpenRCT2::Scripting
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Scripting;
|
||||
|
||||
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 16;
|
||||
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 17;
|
||||
|
||||
struct ExpressionStringifier final
|
||||
{
|
||||
@@ -489,6 +489,7 @@ void ScriptEngine::StopPlugin(std::shared_ptr<Plugin> plugin)
|
||||
if (plugin->HasStarted())
|
||||
{
|
||||
RemoveCustomGameActions(plugin);
|
||||
RemoveIntervals(plugin);
|
||||
RemoveSockets(plugin);
|
||||
_hookEngine.UnsubscribeAll(plugin);
|
||||
for (auto callback : _pluginStoppedSubscriptions)
|
||||
@@ -651,6 +652,7 @@ void ScriptEngine::Update()
|
||||
}
|
||||
}
|
||||
|
||||
UpdateIntervals();
|
||||
UpdateSockets();
|
||||
ProcessREPL();
|
||||
}
|
||||
@@ -1211,6 +1213,95 @@ void ScriptEngine::SaveSharedStorage()
|
||||
}
|
||||
}
|
||||
|
||||
IntervalHandle ScriptEngine::AllocateHandle()
|
||||
{
|
||||
for (size_t i = 0; i < _intervals.size(); i++)
|
||||
{
|
||||
if (!_intervals[i].IsValid())
|
||||
{
|
||||
return static_cast<IntervalHandle>(i + 1);
|
||||
}
|
||||
}
|
||||
_intervals.emplace_back();
|
||||
return static_cast<IntervalHandle>(_intervals.size());
|
||||
}
|
||||
|
||||
IntervalHandle ScriptEngine::AddInterval(const std::shared_ptr<Plugin>& plugin, int32_t delay, bool repeat, DukValue&& callback)
|
||||
{
|
||||
auto handle = AllocateHandle();
|
||||
if (handle != 0)
|
||||
{
|
||||
auto& interval = _intervals[static_cast<size_t>(handle) - 1];
|
||||
interval.Owner = plugin;
|
||||
interval.Handle = handle;
|
||||
interval.Delay = delay;
|
||||
interval.LastTimestamp = _lastIntervalTimestamp;
|
||||
interval.Callback = std::move(callback);
|
||||
interval.Repeat = repeat;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ScriptEngine::RemoveInterval(const std::shared_ptr<Plugin>& plugin, IntervalHandle handle)
|
||||
{
|
||||
if (handle > 0 && static_cast<size_t>(handle) <= _intervals.size())
|
||||
{
|
||||
auto& interval = _intervals[static_cast<size_t>(handle) - 1];
|
||||
|
||||
// Only allow owner or REPL (nullptr) to remove intervals
|
||||
if (plugin == nullptr || interval.Owner == plugin)
|
||||
{
|
||||
interval = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::UpdateIntervals()
|
||||
{
|
||||
uint32_t timestamp = platform_get_ticks();
|
||||
if (timestamp < _lastIntervalTimestamp)
|
||||
{
|
||||
// timestamp has wrapped, subtract all intervals by the remaining amount before wrap
|
||||
auto delta = static_cast<int64_t>(std::numeric_limits<uint32_t>::max() - _lastIntervalTimestamp);
|
||||
for (auto& interval : _intervals)
|
||||
{
|
||||
if (interval.IsValid())
|
||||
{
|
||||
interval.LastTimestamp = -delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
_lastIntervalTimestamp = timestamp;
|
||||
|
||||
for (auto& interval : _intervals)
|
||||
{
|
||||
if (interval.IsValid())
|
||||
{
|
||||
if (timestamp >= interval.LastTimestamp + interval.Delay)
|
||||
{
|
||||
ExecutePluginCall(interval.Owner, interval.Callback, {}, false);
|
||||
|
||||
interval.LastTimestamp = timestamp;
|
||||
if (!interval.Repeat)
|
||||
{
|
||||
RemoveInterval(nullptr, interval.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::RemoveIntervals(const std::shared_ptr<Plugin>& plugin)
|
||||
{
|
||||
for (auto& interval : _intervals)
|
||||
{
|
||||
if (interval.Owner == plugin)
|
||||
{
|
||||
interval = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ifndef DISABLE_NETWORK
|
||||
void ScriptEngine::AddSocket(const std::shared_ptr<ScSocketBase>& socket)
|
||||
{
|
||||
|
||||
@@ -117,6 +117,22 @@ namespace OpenRCT2::Scripting
|
||||
}
|
||||
};
|
||||
|
||||
using IntervalHandle = int32_t;
|
||||
struct ScriptInterval
|
||||
{
|
||||
std::shared_ptr<Plugin> Owner;
|
||||
IntervalHandle Handle{};
|
||||
uint32_t Delay{};
|
||||
int64_t LastTimestamp{};
|
||||
DukValue Callback;
|
||||
bool Repeat{};
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return Handle != 0;
|
||||
}
|
||||
};
|
||||
|
||||
class ScriptEngine
|
||||
{
|
||||
private:
|
||||
@@ -133,6 +149,9 @@ namespace OpenRCT2::Scripting
|
||||
ScriptExecutionInfo _execInfo;
|
||||
DukValue _sharedStorage;
|
||||
|
||||
uint32_t _lastIntervalTimestamp{};
|
||||
std::vector<ScriptInterval> _intervals;
|
||||
|
||||
std::unique_ptr<FileWatcher> _pluginFileWatcher;
|
||||
std::unordered_set<std::string> _changedPluginFiles;
|
||||
std::mutex _changedPluginFilesMutex;
|
||||
@@ -203,6 +222,9 @@ namespace OpenRCT2::Scripting
|
||||
|
||||
void SaveSharedStorage();
|
||||
|
||||
IntervalHandle AddInterval(const std::shared_ptr<Plugin>& plugin, int32_t delay, bool repeat, DukValue&& callback);
|
||||
void RemoveInterval(const std::shared_ptr<Plugin>& plugin, IntervalHandle handle);
|
||||
|
||||
# ifndef DISABLE_NETWORK
|
||||
void AddSocket(const std::shared_ptr<ScSocketBase>& socket);
|
||||
# endif
|
||||
@@ -228,6 +250,10 @@ namespace OpenRCT2::Scripting
|
||||
void InitSharedStorage();
|
||||
void LoadSharedStorage();
|
||||
|
||||
IntervalHandle AllocateHandle();
|
||||
void UpdateIntervals();
|
||||
void RemoveIntervals(const std::shared_ptr<Plugin>& plugin);
|
||||
|
||||
void UpdateSockets();
|
||||
void RemoveSockets(const std::shared_ptr<Plugin>& plugin);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user