File plugin.cpp
File List > src > plugin.cpp
Go to the documentation of this file
#include "illixr.hpp"
#include "illixr/error_util.hpp"
#include "illixr/switchboard.hpp"
#ifdef __ANDROID__
# include
# include
# include
# define _STR(y) #y
# define STRINGIZE(x) _STR(x)
#else
# ifndef ILLIXR_INSTALL_PATH
# error "ILLIXR_INSTALL_PATH must be defined"
# endif
# ifndef BOOST_DATE_TIME_NO_LIB
# define BOOST_DATE_TIME_NO_LIB
# endif
# if defined(ILLIXR_ENABLE_BOBA) && defined(ILLIXR_ENABLE_QUEST_CONTROLLERS)
# include "illixr/network/udpsocket.hpp"
# endif
# include
# include
# include
# include
# include
# include
# ifdef __linux__
# include
# include
# endif
# include
# include
# include
# include
#endif
#ifndef __ANDROID__
namespace ILLIXR {
struct Dependency {
std::string name;
std::map<std::string, std::vector<std::string>> deps;
bool operator==(const Dependency& rhs) const {
return name == rhs.name;
}
bool operator==(const std::string& rhs) const {
return name == rhs;
}
};
} // namespace ILLIXR
# if !defined(__ANDROID__) && defined(ILLIXR_ENABLE_BOBA) && defined(ILLIXR_ENABLE_QUEST_CONTROLLERS)
namespace {
constexpr int QUEST_CONFIGURATION_PORT = 9010;
constexpr const char* QUEST_CONNECT_REQUEST = "ILLIXR_CONNECT_V1";
constexpr const char* QUEST_CONNECT_RESPONSE = "ILLIXR_READY_V1";
void configure_native_quest(const cxxopts::ParseResult& options) {
if (!options.count("quest-ip")) {
return;
}
const std::string quest_ip = options["quest-ip"].as<std::string>();
sockaddr_in parsed_address{};
if (inet_pton(AF_INET, quest_ip.c_str(), &parsed_address.sin_addr) != 1) {
throw std::runtime_error("Invalid Quest IPv4 address: " + quest_ip);
}
const int timeout_seconds = options["quest-connect-timeout"].as<int>();
if (timeout_seconds <= 0 || timeout_seconds > 3600) {
throw std::runtime_error("--quest-connect-timeout must be between 1 and 3600 seconds");
}
ILLIXR::network::UDPSocket socket;
socket.socket_set_reuseaddr();
socket.socket_bind(0);
socket.socket_set_receive_timeout(500);
socket.set_peer(quest_ip, QUEST_CONFIGURATION_PORT);
std::cout << "Waiting for ILLIXRApp at " << quest_ip << ':' << QUEST_CONFIGURATION_PORT
<< ". Open the app on the Quest if it is not already running." << std::endl;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{timeout_seconds};
while (std::chrono::steady_clock::now() < deadline) {
if (!socket.write_data(QUEST_CONNECT_REQUEST)) {
throw std::runtime_error("Could not send wireless configuration to Quest " + quest_ip);
}
sockaddr_in source{};
std::string response = socket.read_data(&source);
if (response == QUEST_CONNECT_RESPONSE && source.sin_addr.s_addr == parsed_address.sin_addr.s_addr) {
std::cout << "ILLIXRApp acknowledged the desktop at " << quest_ip << ". Starting ILLIXR." << std::endl;
errno = 0;
return;
}
}
throw std::runtime_error("Timed out waiting for ILLIXRApp at " + quest_ip +
". Confirm that the app is open and both devices are on the same network.");
}
} // namespace
# endif
namespace YAML {
template<>
struct convert<ILLIXR::Dependency> {
static Node encode(const ILLIXR::Dependency& rhs) {
Node node;
node["plugin"] = rhs.name;
for (const auto& [key, value] : rhs.deps) {
Node dep_node;
dep_node["needs"] = key;
for (const auto& v : value) {
dep_node["provided_by"].push_back(v);
}
node["dependencies"].push_back(dep_node);
}
return node;
}
static bool decode(const Node& node, ILLIXR::Dependency& rhs) {
if (node.size() != 2) {
return false;
}
rhs.name = node["plugin"].as<std::string>();
Node deps = node["dependencies"];
if (!deps.IsSequence()) {
return false;
}
for (const auto& nd : deps) {
if (nd.size() != 2) {
return false;
}
auto dep_name = nd["needs"].as<std::string>();
auto prov = nd["provided_by"].as<std::vector<std::string>>();
rhs.deps[dep_name] = prov;
}
return true;
}
};
} // namespace YAML
#endif
MY_EXPORT_API ILLIXR::runtime* runtime_ = nullptr;
using namespace ILLIXR;
#ifndef __ANDROID__
std::string get_home_dir() {
# if defined(_WIN32) || defined(_WIN64)
char* path = getenv("USERPROFILE");
return {path};
# else
struct passwd* pw = getpwuid(getuid());
return {pw->pw_dir};
# endif
}
void check_plugins(std::vector<std::string>& plugins, const std::vector<ILLIXR::Dependency>& dep_map) {
std::vector<std::string> ordered_plugins;
ordered_plugins.reserve(plugins.size());
auto resolve = [&dep_map, &ordered_plugins, &plugins](std::vector<std::string>::iterator it, auto&& resolve) {
auto find_it = std::find(dep_map.begin(), dep_map.end(), *it);
// if the plugin does not have any dependencies, then just add it to the list
if (find_it == dep_map.end()) {
if (std::find(ordered_plugins.begin(), ordered_plugins.end(), *it) == ordered_plugins.end())
ordered_plugins.push_back(*it);
return false;
}
bool mod = false;
// go through each dependency and see if it is specified
for (const auto& [item, needs] : find_it->deps) {
bool dep_found = false;
// first check plugins which are already in the list, if found, then we are good.
for (const auto& provided_by : needs) {
if (std::find(ordered_plugins.begin(), ordered_plugins.end(), provided_by) != ordered_plugins.end()) {
dep_found = true;
break;
}
}
// try finding it in the rest of the list
if (!dep_found) {
bool rdep_found = false;
for (const auto& provided_by : needs) {
auto r_find_it = std::find(plugins.begin(), plugins.end(), provided_by);
if (r_find_it != plugins.end()) {
rdep_found = true;
mod = true;
resolve(r_find_it, resolve);
if (std::find(ordered_plugins.begin(), ordered_plugins.end(), provided_by) == ordered_plugins.end())
ordered_plugins.push_back(provided_by);
break;
}
}
if (!rdep_found) {
spdlog::get("illixr")->warn(
"Potential missing plugin dependency. Plugin " + *it + " requests a provider of " + item +
" to be included in the plugin list. This can be provided by one of the following plugins: " +
boost::algorithm::join(needs, ", "));
}
}
}
return mod;
};
bool modified = false;
for (auto iter = plugins.begin(); iter != plugins.end(); iter++) {
std::string input = *iter;
modified |= resolve(iter, resolve);
if (std::find(ordered_plugins.begin(), ordered_plugins.end(), *iter) == ordered_plugins.end())
ordered_plugins.push_back(*iter);
}
if (modified)
plugins = ordered_plugins;
}
#endif
int ILLIXR::run(
#ifdef __ANDROID__
const std::vector<std::string>& plugins, struct android_app* app
#else
const cxxopts::ParseResult& options
#endif
) {
std::chrono::seconds run_duration;
#ifndef __ANDROID__
std::vector<std::string> plugins;
#endif
try {
runtime_ = ILLIXR::runtime_factory();
#if !defined(__ANDROID__) && defined(ILLIXR_ENABLE_BOBA) && defined(ILLIXR_ENABLE_QUEST_CONTROLLERS)
configure_native_quest(options);
#endif
// set internal env_vars
std::shared_ptr<switchboard> switchboard_ = runtime_->get_switchboard();
#ifdef __ANDROID__
switchboard_->set_android_app(app);
#else
// read in yaml config file
YAML::Node config;
setenv("ILLIXR_BINARY_PATH", ILLIXR_INSTALL_PATH, 1);
std::string home_dir = get_home_dir();
if (options.count("yaml")) {
std::cout << "Reading " << options["yaml"].as<std::string>() << std::endl;
auto config_file_full = options["yaml"].as<std::string>();
std::string config_file = config_file_full.substr(config_file_full.find_last_of("/\\") + 1);
std::vector<std::string> config_list = {config_file,
config_file_full,
home_dir + "/.illixr/profiles/" + config_file_full,
home_dir + "/.illixr/profiles/" + config_file,
home_dir + "/" + config_file_full,
home_dir + "/" + config_file,
std::string(ILLIXR_INSTALL_PATH) + "/share/illixr/profiles/" +
config_file_full,
std::string(ILLIXR_INSTALL_PATH) + "/share/illixr/profiles/" + config_file};
for (auto& filepath : config_list) {
try {
config = YAML::LoadFile(filepath);
break;
} catch (YAML::BadFile&) { }
}
if (config.size() == 0) {
spdlog::get("illixr")->error("Could not load given config file: " + config_file_full);
throw std::runtime_error("Could not load given config file: " + config_file_full);
}
config_list.clear();
}
// set env vars from config file first, as command line args will override
for (const auto& item : config["env_vars"]) {
const auto val = item.first.as<std::string>();
if (std::find(ignore_vars.begin(), ignore_vars.end(), val) == ignore_vars.end())
switchboard_->set_env(val, item.second.as<std::string>());
}
// command line specified env_vars
for (auto& item : options.unmatched()) {
bool matched = false;
cxxopts::values::parser_tool::ArguDesc ad = cxxopts::values::parser_tool::ParseArgument(item.c_str(), matched);
if (switchboard_->get_env(ad.arg_name, "").empty()) {
if (!ad.set_value)
ad.value = "True";
switchboard_->set_env(ad.arg_name, ad.value);
setenv(ad.arg_name.c_str(), ad.value.c_str(), 1); // env vars from command line take precedence
}
}
#endif
#ifndef NDEBUG
const bool enable_pre_sleep = switchboard_->get_env_bool("ILLIXR_ENABLE_PRE_SLEEP", "False");
if (enable_pre_sleep) {
# if defined(_WIN32) || defined(_WIN64)
DWORD pid = GetCurrentProcessId();
# else
const pid_t pid = getpid();
# endif
spdlog::get("illixr")->info("[main] Pre-sleep enabled.");
spdlog::get("illixr")->info("[main] PID: {}", pid);
spdlog::get("illixr")->info("[main] Sleeping for {} seconds...", ILLIXR_PRE_SLEEP_DURATION);
# if defined(_WIN32) || defined(_WIN64)
Sleep(ILLIXR_PRE_SLEEP_DURATION * 1000);
# else
sleep(ILLIXR_PRE_SLEEP_DURATION);
# endif
spdlog::get("illixr")->info("[main] Resuming...");
}
#endif
#ifdef __ANDROID__
run_duration = (!switchboard_->get_env("ILLIXR_RUN_DURATION").empty())
? std::chrono::seconds{std::stol(std::string{switchboard_->get_env("ILLIXR_RUN_DURATION")})}
: ILLIXR_RUN_DURATION_DEFAULT;
#else
if (options.count("duration")) {
run_duration = std::chrono::seconds{options["duration"].as<long>()};
} else if (config["env_vars"]["duration"]) {
run_duration = std::chrono::seconds{config["env_vars"]["duration"].as<long>()};
} else {
run_duration = (!switchboard_->get_env("ILLIXR_RUN_DURATION").empty())
? std::chrono::seconds{std::stol(std::string{switchboard_->get_env("ILLIXR_RUN_DURATION")})}
: ILLIXR_RUN_DURATION_DEFAULT;
}
GET_STRING(data, ILLIXR_DATA)
GET_STRING(demo_data, ILLIXR_DEMO_DATA)
GET_BOOL(enable_offload, ILLIXR_OFFLOAD_ENABLE)
GET_BOOL(alignment_enable, ILLIXR_ALIGNMENT_ENABLE)
GET_BOOL(enable_verbose_errors, ILLIXR_ENABLE_VERBOSE_ERRORS)
GET_BOOL(enable_pre_sleep, ILLIXR_ENABLE_PRE_SLEEP)
GET_BOOL(openxr, ILLIXR_OPENXR)
GET_STRING(realsense_cam, REALSENSE_CAM)
if (switchboard_->get_env_char("ILLIXR_DISPLAY_MODE") == nullptr) {
spdlog::get("illixr")->info("[main] Display mode not selected, defaulting to GLFW.");
switchboard_->set_env("ILLIXR_DISPLAY_MODE", "glfw");
}
setenv("__GL_MaxFramesAllowed", "1", false);
setenv("__GL_SYNC_TO_VBLANK", "1", false);
std::vector<ILLIXR::Dependency> dep_map;
std::vector<std::string> dep_list = {"plugin_deps.yaml", home_dir + "/.illixr/profiles/plugin_deps.yaml",
std::string(ILLIXR_INSTALL_PATH) + "/share/illixr/profiles/plugin_deps.yaml"
};
bool dep_loaded = false;
for (auto& dep_file : dep_list) {
try {
YAML::Node plugin_deps = YAML::LoadFile(dep_file);
# ifndef NDEBUG
spdlog::get("illixr")->info("Located plugin dependency map file (" + dep_file +
"), verifying plugin dependencies.");
# endif
dep_map.reserve(plugin_deps["dep_map"].size());
for (const auto& node : plugin_deps["dep_map"])
dep_map.push_back(node.as<ILLIXR::Dependency>());
dep_loaded = true;
break;
} catch (YAML::BadFile& bf) { }
}
if (!dep_loaded)
spdlog::get("illixr")->info("Could not load plugin dependency map file, cannot verify plugin dependencies.");
bool have_plugins = false;
// run entry supersedes plugins entry
for (auto item : {"plugins"}) {
if (options.count(item)) {
plugins = options[item].as<std::vector<std::string>>();
have_plugins = true;
} else if (config[item]) {
std::stringstream tss(config[item].as<std::string>());
while (tss.good()) {
std::string substr;
getline(tss, substr, ',');
plugins.push_back(substr);
}
have_plugins = true;
}
}
if (!have_plugins) {
std::cout << "No plugins specified." << std::endl;
std::cout << "A list of plugins must be given on the command line or in a YAML file" << std::endl;
return EXIT_FAILURE;
}
check_plugins(plugins, dep_map);
if (config["install_prefix"]) {
std::string temp_path(switchboard_->get_env("LD_LIBRARY_PATH"));
temp_path = config["install_prefix"].as<std::string>() + ":" + temp_path;
setenv("LD_LIBRARY_PATH", temp_path.c_str(), true);
}
// prevent double free
switchboard_.reset();
#endif
RAC_ERRNO_MSG("main after creating runtime");
std::vector<std::string> lib_paths;
std::transform(plugins.begin(), plugins.end(), std::back_inserter(lib_paths), [](const std::string& arg) {
#if defined(_WIN32) || defined(_WIN64)
return "plugin." + arg + STRINGIZE(ILLIXR_BUILD_SUFFIX) + ".dll";
#else
return "libplugin." + arg + STRINGIZE(ILLIXR_BUILD_SUFFIX) + ".so";
#endif
});
RAC_ERRNO_MSG("main before loading dynamic libraries");
runtime_->load_so(lib_paths);
cancellable_sleep cs;
std::thread th{[&] {
cs.sleep(run_duration);
runtime_->stop();
}};
runtime_->wait(); // blocks until shutdown is runtime_->stop()
// cancel our sleep, so we can join the other thread
cs.cancel();
th.join();
delete runtime_;
} catch (const std::exception& ex) {
std::cout << "ERROR: Exception caught in main: " << ex.what() << std::endl;
delete runtime_;
}
return 0;
}