Skip to content

File switchboard.hpp

File List > illixr > switchboard.hpp

Go to the documentation of this file

#pragma once

#if defined(_WIN32) || defined(_WIN64)
#    include 
#endif
#include "concurrentqueue/blockingconcurrentqueue.hpp"
#include "export.hpp"
#include "managed_thread.hpp"
#include "network/network_backend.hpp"
#include "network/topic_config.hpp"
#include "phonebook.hpp"
#include "record_logger.hpp"

#ifdef Success
#    undef Success // For 'Success' conflict
#endif

#ifdef __ANDROID__
#    include 
#    include 
#else
#    include 
#    include 
#endif
#include 
#include 
#include 
#include 
#include 

#ifdef __ANDROID__
#    include 
#endif

#ifndef NDEBUG
#    include 
#endif

#if __has_include("cpu_timer.hpp")
#    include "cpu_timer.hpp"
#else
static std::chrono::nanoseconds thread_cpu_time() {
    return {};
}
#endif

#include 
#include 
#include 
#include 
#include 

namespace ILLIXR {

using plugin_id_t = std::size_t;

const std::vector<std::string> ignore_vars = {"plugins"};
const std::vector<std::string> ENV_VARS    = {
    "ILLIXR_ENABLE_PRE_SLEEP",
    "ILLIXR_LOG_LEVEL",
    "ILLIXR_RUN_DURATION",
};
const record_header _switchboard_callback_header{
    "switchboard_callback",
    {
        {"plugin_id", typeid(plugin_id_t)},
        {"topic_name", typeid(std::string)},
        {"iteration_no", typeid(std::size_t)},
        {"cpu_time_start", typeid(std::chrono::nanoseconds)},
        {"cpu_time_stop", typeid(std::chrono::nanoseconds)},
        {"wall_time_start", typeid(std::chrono::high_resolution_clock::time_point)},
        {"wall_time_stop", typeid(std::chrono::high_resolution_clock::time_point)},
    }};

const record_header _switchboard_topic_stop_header{"switchboard_topic_stop",
                                                   {
                                                       {"plugin_id", typeid(plugin_id_t)},
                                                       {"topic_name", typeid(std::string)},
                                                       {"enqueued", typeid(std::size_t)},
                                                       {"dequeued", typeid(std::size_t)},
                                                       {"idle_cycles", typeid(std::size_t)},
                                                   }};

class MY_EXPORT_API switchboard : public phonebook::service {
public:
    template<typename Specific_event>
    using ptr = std::shared_ptr<Specific_event>;

    class event {
    public:
        template<typename Archive>
        [[maybe_unused]] void serialize(Archive& ar, const unsigned int version) {
            (void) ar;
            (void) version;
        }

        virtual ~event() = default;
    };

    template<typename Underlying_type>
    class event_wrapper : public event {
    public:
        event_wrapper() = default;

        explicit event_wrapper(Underlying_type underlying_data)
            : underlying_data_{std::move(underlying_data)} { }

        explicit operator Underlying_type() const {
            return underlying_data_;
        }

        Underlying_type& operator*() {
            return underlying_data_;
        }

        const Underlying_type& operator*() const {
            return underlying_data_;
        }

    private:
        Underlying_type underlying_data_;
    };

private:
    class topic_subscription {
    public:
        topic_subscription(const std::string& topic_name, plugin_id_t plugin_id,
                           std::function<void(ptr<const event>&&, std::size_t)> callback,
                           const std::shared_ptr<record_logger>&                record_logger_)
            : topic_name_{topic_name}
            , plugin_id_{plugin_id}
            , callback_{std::move(callback)}
            , record_logger_{record_logger_}
            , cb_log_{record_logger_}
            , thread_{[this] {
                          this->thread_body();
                      },
                      [] {
                          thread_on_start();
                      },
                      [this] {
                          this->thread_on_stop();
                      }} {
            thread_.start();
        }

        void enqueue(ptr<const event>&& this_event) {
            if (thread_.get_state() == managed_thread::state::running) {
                [[maybe_unused]] bool ret = queue_.enqueue(std::move(this_event));
                assert(ret);
                enqueued_++;
            }
        }

    private:
        static void thread_on_start() {
#ifndef NDEBUG
            // spdlog::get("illixr")->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%<%l%$] [switchboard] thread %t %v");
            // spdlog::get("illixr")->debug("start");
            // spdlog::get("illixr")->set_pattern("%+");
#endif
        }

        void thread_body() {
            // Try to pull event off of queue
            ptr<const event> this_event;
            std::int64_t     timeout_usecs = std::chrono::duration_cast<std::chrono::microseconds>(queue_timeout_).count();
            // Note the use of timed blocking wait
            if (queue_.wait_dequeue_timed(token_, this_event, timeout_usecs)) {
                // Process event
                // Also, record and log the time
                dequeued_++;
                auto cb_start_cpu_time  = thread_cpu_time();
                auto cb_start_wall_time = std::chrono::high_resolution_clock::now();
                // std::cerr << "deq " << ptr_to_str(reinterpret_cast(this_event.get_ro())) << " " <<
                // this_event.use_count() << " v\n";
                callback_(std::move(this_event), dequeued_);
                if (cb_log_) {
                    cb_log_.log(record{_switchboard_callback_header,
                                       {
                                           {plugin_id_},
                                           {topic_name_},
                                           {dequeued_},
                                           {cb_start_cpu_time},
                                           {thread_cpu_time()},
                                           {cb_start_wall_time},
                                           {std::chrono::high_resolution_clock::now()},
                                       }});
                }
            } else {
                // Nothing to do.
                idle_cycles_++;
            }
        }

        void thread_on_stop() {
            // Drain queue
            std::size_t unprocessed = enqueued_ - dequeued_;
            {
                ptr<const event> this_event;
                for (std::size_t i = 0; i < unprocessed; ++i) {
                    [[maybe_unused]] bool ret = queue_.try_dequeue(token_, this_event);
                    assert(ret);
                    // std::cerr << "deq (stopping) " << ptr_to_str(reinterpret_cast(this_event.get_ro())) << " "
                    // << this_event.use_count() << " v\n";
                    this_event.reset();
                }
            }

            // Log stats
            if (record_logger_) {
                record_logger_->log(record{_switchboard_topic_stop_header,
                                           {
                                               {plugin_id_},
                                               {topic_name_},
                                               {dequeued_},
                                               {unprocessed},
                                               {idle_cycles_},
                                           }});
            }
        }

        const std::string&                                    topic_name_;
        plugin_id_t                                           plugin_id_;
        std::function<void(ptr<const event>&&, std::size_t)>  callback_;
        const std::shared_ptr<record_logger>                  record_logger_;
        record_coalescer                                      cb_log_;
        moodycamel::BlockingConcurrentQueue<ptr<const event>> queue_{8 /*max size estimate*/};
        moodycamel::ConsumerToken                             token_{queue_};
        static constexpr std::chrono::milliseconds            queue_timeout_{100};
        std::size_t                                           enqueued_{0};
        std::size_t                                           dequeued_{0};
        std::size_t                                           idle_cycles_{0};

        // This needs to be last,
        // so it is destructed before the data it uses.
        managed_thread thread_;
    };

    class topic_buffer {
    public:
        topic_buffer() {
#ifndef NDEBUG
            spdlog::get("illixr")->info("[switchboard] topic buffer created");
#endif
        }

        void enqueue(ptr<const event>&& this_event) {
            queue_size_++;
            [[maybe_unused]] bool ret = queue_.enqueue(std::move(this_event));
            assert(ret);
        }

        [[nodiscard]] size_t size() const {
            return queue_size_;
        }

        ptr<const event> dequeue() {
            ptr<const event> obj;
            queue_size_--;
            queue_.wait_dequeue(token_, obj);
            return obj;
        }

        bool try_dequeue(ptr<const event>& obj) {
            if (!queue_.try_dequeue(token_, obj)) {
                return false;
            }
            queue_size_--;
            return true;
        }

    private:
        moodycamel::BlockingConcurrentQueue<ptr<const event>> queue_{8 /*max size estimate*/};
        moodycamel::ConsumerToken                             token_{queue_};
        std::atomic<size_t>                                   queue_size_{0};
    };

    class topic {
    public:
        topic(std::string name, const std::type_info& ty, std::shared_ptr<record_logger> record_logger_)
            : name_{std::move(name)}
            , type_info_{ty}
            , record_logger_{std::move(record_logger_)}
            , latest_index_{0} { }

        const std::string& name() {
            return name_;
        }

        const std::type_info& ty() {
            return type_info_;
        }

        [[nodiscard]] ptr<const event> get() const {
            size_t           idx        = latest_index_.load() % latest_buffer_size_;
            ptr<const event> this_event = latest_buffer_[idx];
            // if (this_event) {
            //  std::cerr << "get " << ptr_to_str(reinterpret_cast(this_event.get())) << " " <<
            // this_event.use_count() << "v \n";
            // }
            return this_event;
        }

        void put(ptr<const event>&& this_event) {
            assert(this_event != nullptr);
            assert(this_event.use_count() <= 2); 

            /* The pointer that this gets exchanged with needs to get dropped. */
            size_t index          = (latest_index_.load() + 1) % latest_buffer_size_;
            latest_buffer_[index] = this_event;
            latest_index_++;

            // Read/write on subscriptions_.
            // Must acquire shared state on subscriptions_lock_
            std::unique_lock lock{subscriptions_lock_};
            for (topic_subscription& ts : subscriptions_) {
                // std::cerr << "enq " << ptr_to_str(reinterpret_cast(this_event->get())) << " " <<
                // this_event->use_count() << " <\n";
                ptr<const event> event_ptr_copy{this_event};
                ts.enqueue(std::move(event_ptr_copy));
            }

            for (topic_buffer& ts : buffers_) {
                // std::cerr << "enq " << ptr_to_str(reinterpret_cast(this_event->get())) << " " <<
                // this_event->use_count() << " <\n";
                ptr<const event> event_ptr_copy{this_event};
                ts.enqueue(std::move(event_ptr_copy));
            }
            // std::cerr << "put done " << ptr_to_str(reinterpret_cast(this_event->get())) << " " <<
            // this_event->use_count() << " (= 1 + len(sub)) \n";
        }

        [[maybe_unused]] void deserialize_and_put(std::vector<char>& buffer, network::topic_config& config) {
            if (config.serialization_method == network::topic_config::SerializationMethod::BOOST) {
                // TODO: Need to differentiate and support protobuf deserialization
                try {
                    boost::iostreams::stream<boost::iostreams::array_source> stream{buffer.data(), buffer.size()};
                    // Use no_header for cross-platform compatibility (sizeof(long) differs between Windows and Linux)
                    boost::archive::binary_iarchive ia{stream, boost::archive::no_header};
                    ptr<event>                      this_event;
                    ia >> this_event;
                    put(std::move(this_event));
                } catch (const std::exception& e) {
                    std::cerr << "[switchboard] dropping undeserializable message on topic '" << name() << "' ("
                              << buffer.size() << " bytes): " << e.what() << std::endl;
                } catch (...) {
                    std::cerr << "[switchboard] dropping undeserializable message on topic '" << name() << "' ("
                              << buffer.size() << " bytes): unknown exception" << std::endl;
                }
            } else {
                ptr<event> message = std::make_shared<event_wrapper<std::string>>((std::string(buffer.begin(), buffer.end())));
                put(std::move(message));
            }
        }

        void schedule(plugin_id_t plugin_id, const std::function<void(ptr<const event>&&, std::size_t)>& callback) {
            // Write on subscriptions_.
            // Must acquire unique state on subscriptions_lock_
            const std::unique_lock lock{subscriptions_lock_};
            subscriptions_.emplace_back(name_, plugin_id, callback, record_logger_);
        }

        topic_buffer& get_buffer() {
            const std::unique_lock lock{subscriptions_lock_};
            buffers_.emplace_back();
            return buffers_.back();
        }

        void stop() {
            // Write on subscriptions_.
            // Must acquire unique state on subscriptions_lock_
            const std::unique_lock lock{subscriptions_lock_};
            subscriptions_.clear();
        }

    private:
        static constexpr std::size_t latest_buffer_size_ = 256;

        const std::string                                 name_;
        const std::type_info&                             type_info_;
        const std::shared_ptr<record_logger>              record_logger_;
        std::atomic<size_t>                               latest_index_;
        std::array<ptr<const event>, latest_buffer_size_> latest_buffer_;
        std::list<topic_subscription>                     subscriptions_;
        std::list<topic_buffer>                           buffers_;
        std::shared_mutex                                 subscriptions_lock_;
    };

public:
    template<typename Specific_event>
    class reader {
    public:
        explicit reader(topic& topic)
            : topic_{topic} {
#ifndef NDEBUG
            if (typeid(Specific_event) != topic_.ty()) {
                spdlog::get("illixr")->error("[switchboard] topic '{}' holds type {}, but caller used type {}", topic_.name(),
                                             topic_.ty().name(), typeid(Specific_event).name());
                abort();
            }
#endif
        }

        ptr<const Specific_event> get_ro_nullable() const noexcept {
            ptr<const event>          this_event          = topic_.get();
            ptr<const Specific_event> this_specific_event = std::dynamic_pointer_cast<const Specific_event>(this_event);

            if (this_event != nullptr) {
                assert(this_specific_event /* Otherwise, dynamic cast failed; dynamic type information could be wrong*/);
                return this_specific_event;
            } else {
                return ptr<const Specific_event>{nullptr};
            }
        }

        ptr<const Specific_event> get_ro() const {
            ptr<const Specific_event> this_specific_event = get_ro_nullable();
            if (this_specific_event != nullptr) {
                return this_specific_event;
            } else {
                throw std::runtime_error("No event on topic");
            }
        }

        [[maybe_unused]] ptr<Specific_event> get_rw() const {
            /*
              This method is currently not more efficient than calling get_ro() and making a copy,
              but in the future it could be.
             */
            ptr<const Specific_event> this_specific_event = get_ro();
            return std::make_shared<Specific_event>(*this_specific_event);
        }

    private:
        topic& topic_;
    };

    template<typename Specific_event>
    class buffered_reader {
    public:
        explicit buffered_reader(topic& topic)
            : topic_{topic}
            , topic_buffer_{topic_.get_buffer()} { }

        [[nodiscard]] size_t size() const {
            return topic_buffer_.size();
        }

        virtual ptr<const Specific_event> dequeue() {
            // CPU_TIMER_TIME_EVENT_INFO(true, false, "callback", cpu_timer::make_type_eraser("", topic_.name(),
            // serial_no_));
            serial_no_++;
            ptr<const event>          this_event          = topic_buffer_.dequeue();
            ptr<const Specific_event> this_specific_event = std::dynamic_pointer_cast<const Specific_event>(this_event);
            return this_specific_event;
        }

        ptr<const Specific_event> try_dequeue() {
            ptr<const event> this_event;
            if (!topic_buffer_.try_dequeue(this_event)) {
                return nullptr;
            }
            serial_no_++;
            return std::dynamic_pointer_cast<const Specific_event>(this_event);
        }

    private:
        topic&        topic_;
        size_t        serial_no_ = 0;
        topic_buffer& topic_buffer_;
    };

    template<typename Specific_event>
    class writer {
    public:
        explicit writer(topic& topic)
            : topic_{topic} { }

        template<class... Args>
        ptr<Specific_event> allocate(Args&&... args) {
            return std::make_shared<Specific_event>(std::forward<Args>(args)...);
        }

        virtual void put(ptr<Specific_event>&& this_specific_event) {
            assert(typeid(Specific_event) == topic_.ty());
            assert(this_specific_event != nullptr);
            assert(this_specific_event.use_count() == 1);
            ptr<const event> this_event =
                std::const_pointer_cast<const event>(std::static_pointer_cast<event>(std::move(this_specific_event)));
            assert(this_event.use_count() <= 2); 
            topic_.put(std::move(this_event));
        }

    protected:
        // Reference to the underlying topic
        topic& topic_;
    };

    template<typename Serializable_event>
    class network_writer final : public writer<Serializable_event> {
    public:
        explicit network_writer(topic& topic, ptr<network::network_backend> backend = nullptr,
                                const network::topic_config& config = {})
            : writer<Serializable_event>{topic}
            , backend_{std::move(backend)}
            , config_{config} { }

        void put(ptr<Serializable_event>&& this_specific_event) override {
            if (backend_->is_topic_networked(this->topic_.name())) {
                if (config_.serialization_method == network::topic_config::SerializationMethod::BOOST) {
                    auto base_event = std::dynamic_pointer_cast<event>(std::move(this_specific_event));
                    assert(base_event && "Event is not derived from switchboard::event");
                    // Default serialization method - Boost
                    std::vector<char>                                                                        buffer;
                    boost::iostreams::back_insert_device<std::vector<char>>                                  inserter{buffer};
                    boost::iostreams::stream_buffer<boost::iostreams::back_insert_device<std::vector<char>>> stream{inserter};
                    // Use no_header for cross-platform compatibility (sizeof(long) differs between Windows and Linux)
                    boost::archive::binary_oarchive oa{stream, boost::archive::no_header};
                    oa << base_event;
                    // flush
                    stream.pubsync();
                    backend_->topic_send(this->topic_.name(), std::move(std::string(buffer.begin(), buffer.end())));
                } else {
                    // PROTOBUF - this_specific_event will be a string
                    auto        message_ptr = std::dynamic_pointer_cast<event_wrapper<std::string>>(this_specific_event);
                    std::string message     = **message_ptr;
                    backend_->topic_send(this->topic_.name(), std::move(message));
                }
            } else {
                writer<Serializable_event>::put(std::move(this_specific_event));
            }
        }

    private:
        ptr<network::network_backend> backend_;
        network::topic_config         config_;
    };

public:
    explicit switchboard(const phonebook* pb)
        : phonebook_{pb}
        , record_logger_{pb ? pb->lookup_impl<record_logger>() : nullptr} {
        for (const auto& item : ENV_VARS) {
            char* value = getenv(item.c_str());
            if (value) {
                env_vars_[item] = value;
            } else {
                env_vars_[item] = "";
            }
        }
    }

    [[maybe_unused]] bool topic_exists(const std::string& topic_name) {
        const std::shared_lock lock{registry_lock_};
        auto                   found = registry_.find(topic_name);
        return found != registry_.end();
    }

    [[maybe_unused]] topic& get_topic(const std::string& topic_name) {
        const std::shared_lock lock{registry_lock_};
        auto                   found = registry_.find(topic_name);
        if (found != registry_.end()) {
            return found->second;
        } else {
            throw std::runtime_error("Topic not found");
        }
    }

    void set_env(const std::string& var, const std::string& val) {
        env_vars_[var] = val;
        setenv(var.c_str(), val.c_str(), 1);
    }

    std::vector<std::string> env_names() const {
        std::vector<std::string> keys(env_vars_.size());
        std::transform(env_vars_.begin(), env_vars_.end(), keys.begin(), [](auto pair) {
            return pair.first;
        });
        return keys;
    }

    std::string get_env(const std::string& var, std::string _default = "") {
        try {
            if (!env_vars_.at(var).empty())
                return env_vars_.at(var);
            env_vars_.at(var) = _default;
            return _default;
        } catch (std::out_of_range&) {
            char* val = std::getenv(var.c_str());
            if (val) {
                std::string temp(val);
                set_env(var, val); // store it locally for faster retrieval
                return temp;
            }
            return _default;
        }
    }

    bool get_env_bool(const std::string& var, const std::string& def = "false") {
        std::string val = get_env(var, def);
        // see if we are dealing with an int value
        try {
            const int i_val = std::stoi(val);
            if (i_val <= 0)
                return false;
            return true;
        } catch (...) { }

        const std::vector<std::string> affirmative{"yes", "y", "true", "on"};
        for (auto s : affirmative) {
            if (std::equal(val.begin(), val.end(), s.begin(), s.end(), [](char a, char b) {
                    return std::tolower(a) == std::tolower(b);
                }))
                return true;
        }
        return false;
    }

    const char* get_env_char(const std::string& var, const std::string _default = "") {
        std::string val = get_env(var, _default);
        if (val.empty())
            return nullptr;
        return strdup(val.c_str());
    }

    [[maybe_unused]] int get_env_int(const std::string& var, const int _default = 0) {
        std::string val = get_env(var, std::to_string(_default));
        try {
            int res = std::stoi(val);
            return res;
        } catch (...) { }
        return _default;
    }

    [[maybe_unused]] long get_env_long(const std::string& var, const long _default = 0) {
        std::string val = get_env(var, std::to_string(_default));
        try {
            long res = std::stol(val);
            return res;
        } catch (...) { }
        return _default;
    }

    [[maybe_unused]] unsigned long get_env_ulong(const std::string& var, const unsigned long _default = 0) {
        std::string val = get_env(var, std::to_string(_default));
        try {
            unsigned long res = std::stoul(val);
            return res;
        } catch (...) { }
        return _default;
    }

    [[maybe_unused]] double get_env_double(const std::string& var, const double _default = 0.) {
        std::string val = get_env(var, std::to_string(_default));
        try {
            double res = std::stod(val);
            return res;
        } catch (...) { }
        return _default;
    }

    [[maybe_unused]] float get_env_float(const std::string& var, const double _default = 0.f) {
        std::string val = get_env(var, std::to_string(_default));
        try {
            int res = std::stof(val);
            return res;
        } catch (...) { }
        return _default;
    }

    template<typename Specific_event>
    void schedule(plugin_id_t plugin_id, std::string topic_name,
                  std::function<void(ptr<const Specific_event>&&, std::size_t)> fn) {
        try_register_topic<Specific_event>(topic_name)
            .schedule(plugin_id, [=](ptr<const event>&& this_event, std::size_t it_no) {
                assert(this_event);
                ptr<const Specific_event> this_specific_event =
                    std::dynamic_pointer_cast<const Specific_event>(std::move(this_event));
                assert(this_specific_event);
                fn(std::move(this_specific_event), it_no);
            });
    }

    template<typename Specific_event>
    writer<Specific_event> get_writer(const std::string& topic_name) {
        return writer<Specific_event>{try_register_topic<Specific_event>(topic_name)};
    }

    template<typename Specific_event>
    network_writer<Specific_event> get_network_writer(const std::string& topic_name, network::topic_config config = {}) {
        ptr<network::network_backend> backend;

        switch (config.transport_method) {
        case network::topic_config::TransportMethod::UDP:
            backend = phonebook_->lookup_impl<network::udp_backend>();
            if (!backend) {
                throw std::runtime_error("[switchboard] UDP transport requested for topic '" + topic_name +
                                         "' but no UDP network backend is registered in the phonebook. "
                                         "Ensure the udp_network_backend plugin is loaded before this writer is constructed.");
            }
            break;
        case network::topic_config::TransportMethod::TCP:
        default:
            backend = phonebook_->lookup_impl<network::tcp_backend>();
            if (!backend) {
                throw std::runtime_error("[switchboard] TCP transport requested for topic '" + topic_name +
                                         "' but no TCP network backend is registered in the phonebook.");
            }
            break;
        }

        if (registry_.find(topic_name) == registry_.end())
            backend->topic_create(topic_name, config);

        return network_writer<Specific_event>{try_register_topic<Specific_event>(topic_name), backend, config};
    }

    template<typename Specific_event>
    reader<Specific_event> get_reader(const std::string& topic_name) {
        return reader<Specific_event>{try_register_topic<Specific_event>(topic_name)};
    }

    template<typename Specific_event>
    buffered_reader<Specific_event> get_buffered_reader(const std::string& topic_name) {
        return buffered_reader<Specific_event>{try_register_topic<Specific_event>(topic_name)};
    }

    void stop() {
        const std::shared_lock lock{registry_lock_};
        for (auto& pair : registry_) {
            pair.second.stop();
        }
    }

#ifdef __ANDROID__
    void set_android_app(android_app* app) {
        if (app_) {
            spdlog::get("illixr")->error("Android app already set");
            return;
        }
        app_ = app;
    }

    android_app* get_android_app() {
        return app_;
    }
#endif
private:
    const phonebook*                             phonebook_;
    std::unordered_map<std::string, topic>       registry_;
    std::shared_mutex                            registry_lock_;
    std::shared_ptr<record_logger>               record_logger_;
    std::unordered_map<std::string, std::string> env_vars_;
#ifdef __ANDROID__
    android_app* app_ = nullptr;
#endif
    template<typename Specific_event>
    topic& try_register_topic(const std::string& topic_name) {
        {
            const std::shared_lock lock{registry_lock_};
            auto                   found = registry_.find(topic_name);
            if (found != registry_.end()) {
                topic& _topic = found->second;
#ifndef NDEBUG
                if (typeid(Specific_event) != _topic.ty()) {
                    spdlog::get("illixr")->error("[switchboard] topic '{}' holds type {}, but caller used type {}", topic_name,
                                                 _topic.ty().name(), typeid(Specific_event).name());
                    abort();
                }
#endif
                return _topic;
            }
        }

#ifndef NDEBUG
        spdlog::get("illixr")->debug("[switchboard] Creating: {} for {}", topic_name, typeid(Specific_event).name());
#endif
        // Topic not found. Need to create it here.
        const std::unique_lock lock{registry_lock_};
        return registry_.try_emplace(topic_name, topic_name, typeid(Specific_event), record_logger_).first->second;
    }

    class coordinate_system {
    private:
        Eigen::Vector3f    position_;
        Eigen::Quaternionf orientation_;

    public:
        coordinate_system()
            : position_{0., 0., 0.}
            , orientation_{1., 0., 0., 0.} {
            const char* ini_pose = getenv("WCS_ORIGIN");
            // =
            // if (!ini_pose.empty()) {
            if (ini_pose) {
                std::string        ini_pose_str(ini_pose);
                std::stringstream  iss(ini_pose_str);
                std::string        token;
                std::vector<float> ip;
                while (!iss.eof() && std::getline(iss, token, ',')) {
                    ip.emplace_back(std::stof(token));
                }
                if (ip.size() == 3) {
                    position_.x() = ip[0];
                    position_.y() = ip[1];
                    position_.z() = ip[2];
                } else if (ip.size() == 4) {
                    orientation_.w() = ip[0];
                    orientation_.x() = ip[1];
                    orientation_.y() = ip[2];
                    orientation_.z() = ip[3];
                } else if (ip.size() == 7) {
                    position_.x()    = ip[0];
                    position_.y()    = ip[1];
                    position_.z()    = ip[2];
                    orientation_.w() = ip[3];
                    orientation_.x() = ip[4];
                    orientation_.y() = ip[5];
                    orientation_.z() = ip[6];
                }
            }
        }

        [[nodiscard]] const Eigen::Vector3f& position() const {
            return position_;
        }

        [[nodiscard]] const Eigen::Quaternionf& orientation() const {
            return orientation_;
        }
    };

public:
    coordinate_system root_coordinates; 
};

} // namespace ILLIXR