File runtime_impl.cpp
File List > src > runtime_impl.cpp
Go to the documentation of this file
#include "illixr/runtime.hpp"
#include "illixr/dynamic_lib.hpp"
#include "illixr/error_util.hpp"
// #include "illixr/extended_window.hpp"
#include "illixr/global_module_defs.hpp"
#include "illixr/phonebook.hpp"
#include "illixr/plugin.hpp"
#include "illixr/record_logger.hpp"
#include "illixr/stoplight.hpp"
#include "illixr/switchboard.hpp"
#ifndef __ANDROID__
# include "illixr/vk/vk_extension_request.hpp"
# include "vulkan_display.hpp"
#endif
// #include "sqlite_record_logger.hpp"
// #include "stdout_record_logger.hpp"
#include "no_op_record_logger.hpp"
#include
#include
#include
#ifndef __ANDROID__
# include
#endif
#include
#ifdef __ANDROID__
# include
#else
# include
# include
#endif
#include
#include
#if defined(_WIN32) || defined(_WIN64)
# include
#endif
using namespace ILLIXR;
#ifndef __ANDROID__
typedef bool (*n_monado_t)();
#endif
void spdlogger(const std::string& name, const char* log_level) {
if (!log_level) {
#ifdef NDEBUG
log_level = "warn";
#else
log_level = "debug";
#endif
}
std::vector<spdlog::sink_ptr> sinks;
#ifdef __ANDROID__
sinks.push_back(std::make_shared<spdlog::sinks::android_sink_mt>());
#else
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/" + name + ".log"));
#endif
auto logger = std::make_shared<spdlog::logger>(name, begin(sinks), end(sinks));
logger->set_level(spdlog::level::from_str(log_level));
spdlog::register_logger(logger);
}
class MY_EXPORT_API runtime_impl : public runtime {
public:
explicit runtime_impl() {
spdlogger("illixr", std::getenv("ILLIXR_LOG_LEVEL")); // can't use switchboard interface here
phonebook_.register_impl<relative_clock>(std::make_shared<relative_clock>());
phonebook_.register_impl<record_logger>(std::make_shared<no_op_record_logger>());
phonebook_.register_impl<switchboard>(std::make_shared<switchboard>(&phonebook_));
#if !defined(ENABLE_MONADO) && !defined(__ANDROID__)
// phonebook_.register_impl(
// std::make_shared(display_params::width_pixels, display_params::height_pixels, nullptr));
#endif
switchboard_ = phonebook_.lookup_impl<switchboard>();
#ifndef __ANDROID__
enable_monado_ = false;
#endif
phonebook_.register_impl<stoplight>(std::make_shared<stoplight>());
}
void load_so(const std::vector<std::string>& so_paths) override {
RAC_ERRNO_MSG("runtime_impl before creating any dynamic library");
// Reorder so_paths so any plugin whose name contains "network_backend"
// comes first. This ensures network plugins are started before all others
// on Android, where they must be running before other plugins initialize.
std::vector<std::string> ordered_paths;
ordered_paths.reserve(so_paths.size());
// First pass: collect network_backend plugins and record the count
int network_plugin_count = 0;
for (const auto& path : so_paths) {
if (path.find("network_backend") != std::string::npos) {
ordered_paths.push_back(path);
++network_plugin_count;
}
}
// Second pass: collect all remaining plugins
for (const auto& path : so_paths) {
if (path.find("network_backend") == std::string::npos)
ordered_paths.push_back(path);
}
std::transform(so_paths.cbegin(), so_paths.cend(), std::back_inserter(libraries_), [](const auto& so_path) {
RAC_ERRNO_MSG("runtime_impl before creating the dynamic library");
return dynamic_lib::create(so_path);
});
#ifndef __ANDROID__
for (auto& i : libraries_) {
enable_monado_ = enable_monado_ || i.get<n_monado_t>("needs_monado")();
}
#endif
RAC_ERRNO_MSG("runtime_impl after creating the dynamic libraries");
std::vector<plugin_factory> plugin_factories;
std::transform(libraries_.cbegin(), libraries_.cend(), std::back_inserter(plugin_factories), [](const auto& lib) {
return lib.template get<plugin* (*) (phonebook*)>("this_plugin_factory");
});
#ifndef __ANDROID__
if (!enable_monado_) {
// get env var ILLIXR_DISPLAY_MODE
std::string display_mode =
switchboard_->get_env_char("ILLIXR_DISPLAY_MODE") ? switchboard_->get_env_char("ILLIXR_DISPLAY_MODE") : "glfw";
if (display_mode != "none")
phonebook_.register_impl<vulkan::display_provider>(std::make_shared<display_vk>(&phonebook_));
}
#endif
RAC_ERRNO_MSG("runtime_impl after generating plugin factories");
phonebook_.lookup_impl<relative_clock>()->start();
#ifdef __ANDROID__ // on Android we have to have the network plugins up and running right away
// Start network backend plugins first — they must be running before
// any other plugin initializes. Count was determined by the reorder above.
for (int i = 0; i < network_plugin_count; ++i) {
plugins_.push_back(std::unique_ptr<plugin>{plugin_factories[i](&phonebook_)});
plugins_[i]->start();
}
#else
network_plugin_count = 0; // unused on non-Android, keeps offset logic unified#endif
#endif
std::transform(plugin_factories.cbegin() + network_plugin_count, plugin_factories.cend(), std::back_inserter(plugins_),
[this](const auto& plugin_factory) {
RAC_ERRNO_MSG("runtime_impl before building the plugin");
try {
#ifdef __ANDROID__
return std::unique_ptr<plugin>{plugin_factory(&phonebook_)};
#else
return std::unique_ptr<plugin>{plugin_factory(&this->phonebook_)};
#endif
} catch (std::exception& ex) {
spdlog::get("illixr")->error(ex.what());
throw;
}
});
#ifndef __ANDROID__
if (!enable_monado_) {
const std::string display_mode =
switchboard_->get_env_char("ILLIXR_DISPLAY_MODE") ? switchboard_->get_env_char("ILLIXR_DISPLAY_MODE") : "glfw";
if (display_mode != "none") {
std::set<const char*> instance_extensions;
std::set<const char*> device_extensions;
std::for_each(plugins_.cbegin(), plugins_.cend(), [&](const auto& plugin) {
auto requester = std::dynamic_pointer_cast<ILLIXR::vulkan::vk_extension_request>(plugin);
if (requester != nullptr) {
auto requested_instance_extensions = requester->get_required_instance_extensions();
instance_extensions.insert(requested_instance_extensions.begin(), requested_instance_extensions.end());
auto requested_device_extensions = requester->get_required_devices_extensions();
device_extensions.insert(requested_device_extensions.begin(), requested_device_extensions.end());
}
});
auto display = std::static_pointer_cast<display_vk>(phonebook_.lookup_impl<vulkan::display_provider>());
display->start(instance_extensions, device_extensions);
}
}
#endif
std::for_each(plugins_.cbegin() + network_plugin_count, plugins_.cend(), [](const auto& plugin) {
// Well-behaved plugins_ (any derived from threadloop) start there threads here, and then wait on the Stoplight.
plugin->start();
});
// This actually kicks off the plugins
phonebook_.lookup_impl<stoplight>()->signal_ready();
}
void load_so(const std::string_view& so) override {
auto lib = dynamic_lib::create(so);
auto this_plugin_factory = lib.get<plugin* (*) (phonebook*)>("this_plugin_factory");
load_plugin_factory(this_plugin_factory);
libraries_.push_back(std::move(lib));
}
void load_plugin_factory(plugin_factory plugin_main) override {
plugins_.emplace_back(plugin_main(&phonebook_));
plugins_.back()->start();
}
void wait() override {
const std::shared_ptr<stoplight> stoplight = phonebook_.lookup_impl<ILLIXR::stoplight>();
// Plugins request an application-wide shutdown by signaling should_stop.
// Coordinate the actual stop from this caller thread so a requesting
// plugin never attempts to join its own worker thread.
stoplight->wait_for_should_stop();
_stop();
// Do not return until every plugin thread has been joined.
stoplight->wait_for_shutdown_complete();
}
void _stop() override {
std::call_once(stop_once_, [this]() {
phonebook_.lookup_impl<stoplight>()->signal_should_stop();
// After this point, threads may exit their main loops
// They still have destructors and still have to be joined.
phonebook_.lookup_impl<switchboard>()->stop();
// After this point, Switchboard's internal thread-workers which power synchronous callbacks are stopped and joined.
// Stop consumers before the services they depend on. In particular,
// network producers must finish before their backend sockets close.
for (auto plugin = plugins_.rbegin(); plugin != plugins_.rend(); ++plugin) {
(*plugin)->stop();
// Each plugin gets joined in its stop
}
// Tell runtime::wait() that it can return
phonebook_.lookup_impl<stoplight>()->signal_shutdown_complete();
});
}
~runtime_impl() override {
if (!phonebook_.lookup_impl<stoplight>()->check_shutdown_complete()) {
stop();
}
// The base runtime also retains the switchboard. Release that reference
// now so phonebook-owned topics are destroyed before plugin libraries
// (which supplied some inline queue code) are unloaded.
switchboard_.reset();
// This will be re-enabled in #225
// assert(errno == 0 && "errno was set during run. Maybe spurious?");
/*
Note that this assertion can have false positives AND false negatives!
- False positive because the contract of some functions specifies that errno is only meaningful if the return code was
an error [1].
- We will try to mitigate this by clearing errno on success in ILLIXR.
- False negative if some intervening call clears errno.
- We will try to mitigate this by checking for errors immediately after a call.
Despite these mitigations, the best way to catch errors is to check errno immediately after a calling function.
[1] https://cboard.cprogramming.com/linux-programming/119957-xlib-perversity.html
*/
}
private:
// I have to keep the dynamic libraries in scope until the program is dead
std::vector<dynamic_lib> libraries_;
phonebook phonebook_;
std::vector<std::shared_ptr<plugin>> plugins_;
std::once_flag stop_once_;
};
extern "C" [[maybe_unused]] MY_EXPORT_API runtime* runtime_factory() {
RAC_ERRNO_MSG("runtime_impl before creating the runtime");
return new runtime_impl{};
}