Allow changing the event loop tick rate

This also changes the refresh rate for most plugins. You can now lower
this setting if your computer is struggling to keep up with rendering a
certain heavy plugin.
This commit is contained in:
Robbert van der Helm
2021-01-11 23:38:21 +01:00
parent b86df213fb
commit bf3a4e7296
10 changed files with 84 additions and 13 deletions
+11
View File
@@ -96,6 +96,12 @@ Configuration::Configuration(const fs::path& config_path,
} else {
invalid_options.push_back(key);
}
} else if (key == "frame_rate") {
if (const auto parsed_value = value.as_floating_point()) {
frame_rate = parsed_value->get();
} else {
invalid_options.push_back(key);
}
} else if (key == "group") {
if (const auto parsed_value = value.as_string()) {
group = parsed_value->get();
@@ -110,3 +116,8 @@ Configuration::Configuration(const fs::path& config_path,
break;
}
}
std::chrono::steady_clock::duration Configuration::event_loop_interval() const {
return std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::milliseconds(1000) / frame_rate.value_or(60.0));
}
+22
View File
@@ -22,6 +22,7 @@
#include <boost/filesystem.hpp>
#include <bitsery/ext/std_optional.h>
#include <chrono>
#include <optional>
#include "bitsery/ext/boost-path.h"
@@ -109,6 +110,18 @@ class Configuration {
*/
bool editor_xembed = false;
/**
* The number of times per second we'll handle the event loop. In most
* plugins this also controls the plugin editor GUI's refresh rate.
*
* This defaults to 60 fps, but we'll store it in an optional as we only
* want to show it in the startup message if this setting has explicitly
* been set.
*
* @relates event_loop_interval
*/
std::optional<float> frame_rate;
/**
* The name of the plugin group that should be used for the plugin this
* configuration object was created for. If not set, then the plugin should
@@ -139,13 +152,22 @@ class Configuration {
*/
std::vector<std::string> unknown_options;
/**
* The delay in milliseconds between calls to the event loop and to
* `effEditIdle` for VST2 plugins. This is based on `frame_rate`.
*/
std::chrono::steady_clock::duration event_loop_interval() const;
template <typename S>
void serialize(S& s) {
s.value1b(cache_time_info);
s.value1b(editor_double_embed);
s.value1b(editor_xembed);
s.ext(frame_rate, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.value4b(v); });
s.ext(group, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.text1b(v, 4096); });
s.ext(matched_file, bitsery::ext::StdOptional(),
[](S& s, auto& v) { s.ext(v, bitsery::ext::BoostPath{}); });
s.ext(matched_pattern, bitsery::ext::StdOptional(),
+8
View File
@@ -16,6 +16,8 @@
#pragma once
#include <iomanip>
// Generated inside of the build directory
#include <src/common/config/config.h>
#include <src/common/config/version.h>
@@ -163,6 +165,12 @@ class PluginBridge {
if (config.editor_xembed) {
other_options.push_back("editor: XEmbed");
}
if (config.frame_rate) {
std::ostringstream option;
option << "frame rate: " << std::setprecision(2)
<< *config.frame_rate << " fps";
other_options.push_back(option.str());
}
if (!other_options.empty()) {
init_msg << join_quoted_strings(other_options) << std::endl;
} else {
+3
View File
@@ -130,6 +130,9 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
// configuration as a response
config = sockets.host_vst_control.receive_single<Configuration>();
// Allow this plugin to configure the main context's tick rate
main_context.update_timer_interval(config.event_loop_interval());
parameters_handler = Win32Thread([&]() {
sockets.host_vst_parameters.receive_multi<Parameter>(
[&](Parameter request, std::vector<uint8_t>& buffer) {
+3
View File
@@ -75,6 +75,9 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context,
// process
config = sockets.vst_host_callback.send_message(WantsConfiguration{},
std::nullopt);
// Allow this plugin to configure the main context's tick rate
main_context.update_timer_interval(config.event_loop_interval());
}
void Vst3Bridge::run() {
+1 -1
View File
@@ -145,7 +145,7 @@ Editor::Editor(const Configuration& config,
win32_handle.get(),
idle_timer_id,
std::chrono::duration_cast<std::chrono::milliseconds>(
event_loop_interval)
config.event_loop_interval())
.count())
: Win32Timer()),
idle_timer_proc(std::move(timer_proc)),
+5
View File
@@ -26,6 +26,11 @@ void MainContext::stop() {
context.stop();
}
void MainContext::update_timer_interval(
std::chrono::steady_clock::duration new_interval) {
timer_interval = new_interval;
}
uint32_t WINAPI
win32_thread_trampoline(fu2::unique_function<void()>* entry_point) {
(*entry_point)();
+24 -12
View File
@@ -34,14 +34,6 @@
#include "../common/utils.h"
/**
* The delay between calls to the event loop so we can keep a nice 60 fps. We
* could bump this up to the monitor's refresh rate, but I'm afraid that it will
* start to noticeably take up resources in plugin groups.
*/
constexpr std::chrono::duration event_loop_interval =
std::chrono::milliseconds(1000) / 60;
/**
* A wrapper around `boost::asio::io_context()` to serve as the application's
* main IO context. A single instance is shared for all plugins in a plugin
@@ -66,6 +58,14 @@ class MainContext {
*/
void stop();
/**
* Set a new timer interval. We'll do this whenever a new plugin loads,
* because we can't know in advance what the plugin's frame rate option is
* set to.
*/
void update_timer_interval(
std::chrono::steady_clock::duration new_interval);
/**
* Asynchronously execute a function inside of this main IO context and
* return the results as a future. This is used to make sure that operations
@@ -91,7 +91,9 @@ class MainContext {
}
/**
* Start a timer to handle events every `event_loop_interval` milliseconds.
* Start a timer to handle events on a user configurable interval. The
* interval is controllable through the `frame_rate` option and defaults to
* 60 updates per second.
*
* @param handler The function that should be executed in the IO context
* when the timer ticks. This should be a function that handles both the
@@ -101,9 +103,9 @@ class MainContext {
void async_handle_events(F handler) {
// Try to keep a steady framerate, but add in delays to let other events
// get handled if the GUI message handling somehow takes very long.
events_timer.expires_at(std::max(
events_timer.expiry() + event_loop_interval,
std::chrono::steady_clock::now() + std::chrono::milliseconds(5)));
events_timer.expires_at(
std::max(events_timer.expiry() + timer_interval,
std::chrono::steady_clock::now() + timer_interval / 4));
events_timer.async_wait(
[&, handler](const boost::system::error_code& error) {
if (error.failed()) {
@@ -137,6 +139,16 @@ class MainContext {
* The timer used to periodically handle X11 events and Win32 messages.
*/
boost::asio::steady_timer events_timer;
/**
* The time between timer ticks in `async_handle_events`. This gets
* initialized at 60 ticks per second, and when a plugin load we'll update
* this value based on the plugin's `frame_rate` option.
*
* @see update_timer_interval
*/
std::chrono::steady_clock::duration timer_interval =
std::chrono::milliseconds(1000) / 60;
};
/**