|
|
@@ -1,101 +1,159 @@
|
|
|
#include "transform_tree.h"
|
|
|
|
|
|
-#include "core/sophiar_manager.h"
|
|
|
+#include "core/basic_obj_types.hpp"
|
|
|
+#include "utility/bit_operations.hpp"
|
|
|
#include "utility/coro_worker.hpp"
|
|
|
#include "utility/coro_signal_group.hpp"
|
|
|
-#include "utility/global_obj_helper.hpp"
|
|
|
#include "utility/named_vector.hpp"
|
|
|
-#include "utility/signal_muxer.hpp"
|
|
|
+
|
|
|
+#include <boost/asio/co_spawn.hpp>
|
|
|
+#include <boost/asio/detached.hpp>
|
|
|
|
|
|
#include <fmt/format.h>
|
|
|
|
|
|
+#include <Eigen/Geometry>
|
|
|
+
|
|
|
#include <cassert>
|
|
|
+#include <optional>
|
|
|
#include <string>
|
|
|
#include <vector>
|
|
|
+#include <utility>
|
|
|
|
|
|
namespace sophiar {
|
|
|
|
|
|
- struct transform_tree::impl {
|
|
|
+ using namespace Eigen;
|
|
|
|
|
|
- using trans_update_slot_type = tiny_signal<trans_obj::pointer>::slot_type;
|
|
|
- using trans_updated_signal_type = tiny_signal<>;
|
|
|
+ using boost::asio::co_spawn;
|
|
|
+ using boost::asio::detached;
|
|
|
|
|
|
- struct node_type {
|
|
|
- index_type parent = -1, depth = 0; // 父亲的编号, 节点深度
|
|
|
- bool is_valid = true; // 变换矩阵是否有效
|
|
|
- trans_type trans; // 在父亲下观察自己的结果
|
|
|
- trans_updated_signal_type updated_signal; // 当发生有效变换时发送信号
|
|
|
- };
|
|
|
+ struct transform_tree::impl {
|
|
|
|
|
|
- struct trans_update_slot_impl_type : public trans_update_slot_type {
|
|
|
+ using node_index_type = uint8_t;
|
|
|
+ using trans_type = Isometry3d;
|
|
|
+ static_assert(std::is_same<trans_type, decltype(std::declval<transform_obj>().value)>());
|
|
|
|
|
|
- node_type *node = nullptr;
|
|
|
+ struct node_type { // 在父亲下观察自己
|
|
|
+ node_index_type parent = -1, depth = 0; // 父亲的编号, 节点深度
|
|
|
+ global_obj_index_type transform_obj_index = -1;
|
|
|
+ trans_type fixed_transform; // 如果不提供 transform_obj_index, 则表示这是一个固定变换
|
|
|
+ };
|
|
|
|
|
|
- void on_signal_received(trans_obj::pointer obj) override {
|
|
|
- assert(node != nullptr);
|
|
|
- coordinate_pose_transform_tree trans;
|
|
|
- *obj >> trans;
|
|
|
- bool need_emit_updated_signal = false;
|
|
|
- if (!node->trans.isApprox(trans.view_as_base())) {
|
|
|
- need_emit_updated_signal = true;
|
|
|
- node->trans = trans.view_as_base();
|
|
|
- }
|
|
|
- if (node->is_valid != trans.is_valid) {
|
|
|
- need_emit_updated_signal = true;
|
|
|
- node->is_valid = trans.is_valid;
|
|
|
- }
|
|
|
- if (need_emit_updated_signal) {
|
|
|
- node->updated_signal.emit();
|
|
|
- }
|
|
|
- }
|
|
|
+ struct query_scheme {
|
|
|
+ std::list<node_index_type> left, right;
|
|
|
};
|
|
|
|
|
|
- transform_tree *q_this = nullptr;
|
|
|
- named_vector<index_type, node_type> nodes;
|
|
|
+ named_vector<node_index_type, node_type> nodes;
|
|
|
+ std::vector<coro_worker::pointer> worker_list;
|
|
|
|
|
|
- std::tuple<bool, trans_type> query_transform(index_type observer, index_type target) {
|
|
|
- if (observer == target) [[unlikely]] {
|
|
|
- return {true, trans_type::Identity()};
|
|
|
+ static trans_type transform_from_json_array(const nlohmann::json &arr) {
|
|
|
+ assert(arr.is_array());
|
|
|
+ assert(arr.size() == 7);
|
|
|
+ double val[7];
|
|
|
+ for (size_t i = 0; i < 7; ++i) {
|
|
|
+ assert(arr[i].is_number());
|
|
|
+ val[i] = arr[i].get<double>();
|
|
|
}
|
|
|
+ return Translation3d(val[0], val[1], val[2]) *
|
|
|
+ Quaterniond(val[3], val[4], val[5], val[6]);
|
|
|
+ }
|
|
|
|
|
|
- if (nodes[target].parent == observer) [[unlikely]] {
|
|
|
- return {nodes[target].is_valid, nodes[target].trans};
|
|
|
+ std::optional<trans_type> get_transform_from_path(const std::list<node_index_type> &path) const {
|
|
|
+ auto ret = trans_type::Identity();
|
|
|
+ for (auto index: path) {
|
|
|
+ const auto &node_info = nodes[index];
|
|
|
+ if (!is_valid_id(node_info.transform_obj_index)) {
|
|
|
+ ret = ret * node_info.fixed_transform;
|
|
|
+ } else {
|
|
|
+ auto trans_obj = get_manager().
|
|
|
+ get_global_obj<transform_obj>(node_info.transform_obj_index);
|
|
|
+ if (trans_obj == nullptr) return std::nullopt;
|
|
|
+ ret = ret * trans_obj->value;
|
|
|
+ }
|
|
|
}
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
|
|
|
- if (nodes[observer].parent == target) [[unlikely]] {
|
|
|
- return {nodes[observer].is_valid, nodes[observer].trans.inverse()};
|
|
|
- }
|
|
|
+ std::optional<trans_type> get_transform_from_scheme(const query_scheme &scheme) const {
|
|
|
+ auto left_trans = get_transform_from_path(scheme.left);
|
|
|
+ auto right_trans = get_transform_from_path(scheme.right);
|
|
|
+ if (!left_trans.has_value() || !right_trans.has_value()) return std::nullopt;
|
|
|
+ return left_trans.value().inverse() * right_trans.value();
|
|
|
+ }
|
|
|
|
|
|
- auto observer_trans = trans_type::Identity();
|
|
|
- auto target_trans = trans_type::Identity();
|
|
|
- auto is_valid = true;
|
|
|
+ void create_worker(node_index_type observer,
|
|
|
+ node_index_type target,
|
|
|
+ global_obj_index_type out_obj_index) {
|
|
|
+ assert(is_valid_id(observer) && is_valid_id(target));
|
|
|
+ assert(observer != target);
|
|
|
+
|
|
|
+ query_scheme scheme;
|
|
|
+ auto signal_group = coro_signal_any_group::new_instance(get_context());
|
|
|
while (observer != target) {
|
|
|
- bool is_target_lower = nodes[target].depth > nodes[observer].depth;
|
|
|
- auto &lower = is_target_lower ? target : observer;
|
|
|
- auto &lower_trans = is_target_lower ? target_trans : observer_trans;
|
|
|
- lower_trans = nodes[lower].trans * lower_trans;
|
|
|
- is_valid &= nodes[lower].is_valid;
|
|
|
- lower = nodes[lower].parent;
|
|
|
- assert(lower != -1);
|
|
|
+ const auto &obs_info = nodes[observer];
|
|
|
+ const auto &tar_info = nodes[target];
|
|
|
+ if (obs_info.depth > tar_info.depth) {
|
|
|
+ assert(is_valid_id(obs_info.parent));
|
|
|
+ scheme.left.push_front(observer);
|
|
|
+ if (is_valid_id(obs_info.transform_obj_index)) {
|
|
|
+ signal_group->add_watcher(
|
|
|
+ get_manager().request_global_obj_update_watcher(
|
|
|
+ obs_info.transform_obj_index));
|
|
|
+ }
|
|
|
+ observer = obs_info.parent;
|
|
|
+ } else { // target is deeper
|
|
|
+ assert(is_valid_id(tar_info.parent));
|
|
|
+ scheme.right.push_front(target);
|
|
|
+ if (is_valid_id(tar_info.transform_obj_index)) {
|
|
|
+ signal_group->add_watcher(
|
|
|
+ get_manager().request_global_obj_update_watcher(
|
|
|
+ tar_info.transform_obj_index));
|
|
|
+ }
|
|
|
+ target = tar_info.parent;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- return {is_valid, observer_trans.inverse() * target_trans};
|
|
|
+ signal_group->start(get_context());
|
|
|
+ auto signal_watcher = signal_group->new_watcher(get_context());
|
|
|
+ auto exit_func = [signal_group = std::move(signal_group)]() mutable {
|
|
|
+ co_spawn(global_context, [
|
|
|
+ signal_group = std::move(signal_group)]() mutable
|
|
|
+ -> boost::asio::awaitable<void> {
|
|
|
+ co_await signal_group->stop();
|
|
|
+ co_return;
|
|
|
+ }, detached);
|
|
|
+ };
|
|
|
+
|
|
|
+ auto worker_func = [
|
|
|
+ this, out_obj_index,
|
|
|
+ scheme = std::move(scheme),
|
|
|
+ signal_watcher = std::move(signal_watcher)]() mutable
|
|
|
+ -> boost::asio::awaitable<bool> {
|
|
|
+ co_await signal_watcher.coro_wait();
|
|
|
+ auto new_trans = get_transform_from_scheme(scheme);
|
|
|
+ get_manager().update_global_obj<transform_obj>(
|
|
|
+ out_obj_index,
|
|
|
+ new_trans.has_value() ? transform_obj::new_instance(std::move(new_trans.value())) : nullptr);
|
|
|
+ co_return true;
|
|
|
+ };
|
|
|
+ auto worker = make_infinite_coro_worker(get_context(),
|
|
|
+ std::move(worker_func),
|
|
|
+ std::move(exit_func)); // signal group will be delete here
|
|
|
+ worker->run();
|
|
|
+ worker_list.push_back(std::move(worker));
|
|
|
}
|
|
|
|
|
|
- trans_updated_signal_type *create_watch(index_type observer, index_type target) {
|
|
|
- auto signal = new signal_muxer<>(); // 因为只会调用有限次,所以无视内存泄漏的问题
|
|
|
- while (observer != target) {
|
|
|
- bool is_target_lower = nodes[target].depth > nodes[observer].depth;
|
|
|
- auto &lower = is_target_lower ? target : observer;
|
|
|
- auto lower_name = nodes.to_name_by_index(lower);
|
|
|
- signal->add_upstream_signal(lower_name, nodes[lower].updated_signal);
|
|
|
- lower = nodes[lower].parent;
|
|
|
- assert(lower != -1);
|
|
|
+ boost::asio::awaitable<void> stop_workers() {
|
|
|
+ for (auto &worker: worker_list) {
|
|
|
+ worker->cancel();
|
|
|
+ }
|
|
|
+ for (auto &worker: worker_list) {
|
|
|
+ co_await worker->coro_wait_stop();
|
|
|
}
|
|
|
- return signal;
|
|
|
+ worker_list.clear();
|
|
|
+ co_return;
|
|
|
}
|
|
|
|
|
|
- void load_nodes(const nlohmann::json &config) {
|
|
|
+ void build_tree(const nlohmann::json &config) {
|
|
|
assert(config.contains("node_list"));
|
|
|
assert(config["node_list"].is_array());
|
|
|
for (auto &node: config["node_list"]) {
|
|
|
@@ -105,82 +163,83 @@ namespace sophiar {
|
|
|
auto name = node["name"].get<std::string>();
|
|
|
auto index = nodes.new_elem(name);
|
|
|
|
|
|
- index_type parent_index = -1;
|
|
|
+ node_index_type parent_index = -1;
|
|
|
if (node.contains("parent")) {
|
|
|
assert(node["parent"].is_string());
|
|
|
auto parent_name = node["parent"].get<std::string>();
|
|
|
parent_index = nodes.to_index_by_name(parent_name);
|
|
|
}
|
|
|
|
|
|
- trans_type default_trans = trans_type::Identity();
|
|
|
- if (node.contains("transform")) {
|
|
|
- coordinate_pose_compact compact_trans;
|
|
|
- compact_trans.fill_from_json(node["transform"]);
|
|
|
- default_trans = compact_trans.view_as_base();
|
|
|
+ global_obj_index_type transform_obj_index = -1;
|
|
|
+ if (node.contains("transform_obj_name")) {
|
|
|
+ assert(node["transform_obj_name"].is_string());
|
|
|
+ auto transform_obj_name = node["transform_obj_name"].get<std::string>();
|
|
|
+ transform_obj_index = get_manager().
|
|
|
+ register_global_obj<transform_obj>(transform_obj_name);
|
|
|
}
|
|
|
|
|
|
- bool is_valid = true;
|
|
|
- if (node.contains("valid")) {
|
|
|
- assert(node["valid"].is_boolean());
|
|
|
- is_valid = node["valid"].get<bool>();
|
|
|
+ trans_type default_trans = trans_type::Identity();
|
|
|
+ if (node.contains("transform")) {
|
|
|
+ default_trans = transform_from_json_array(node["transform"]);
|
|
|
+ } else if (is_valid_id(parent_index)) {
|
|
|
+ assert(is_valid_id(transform_obj_index));
|
|
|
}
|
|
|
|
|
|
- auto &tree_node = nodes[index];
|
|
|
- tree_node.parent = parent_index;
|
|
|
- tree_node.is_valid = is_valid;
|
|
|
- tree_node.trans = default_trans;
|
|
|
- if (parent_index != -1) {
|
|
|
- tree_node.depth = nodes[parent_index].depth + 1;
|
|
|
+ auto &node_info = nodes[index];
|
|
|
+ node_info.parent = parent_index;
|
|
|
+ node_info.transform_obj_index = transform_obj_index;
|
|
|
+ node_info.fixed_transform = default_trans;
|
|
|
+ if (is_valid_id(parent_index)) {
|
|
|
+ node_info.depth = nodes[parent_index].depth + 1;
|
|
|
}
|
|
|
-
|
|
|
- auto trans_update_slot = new trans_update_slot_impl_type;
|
|
|
- trans_update_slot->node = &tree_node;
|
|
|
-// get_manager().register_slot<trans_obj::pointer>(q_this, name, *trans_update_slot);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- void load_watches(const nlohmann::json &config) {
|
|
|
- if (!config.contains("watch_list")) return;
|
|
|
+ void config_watcher(const nlohmann::json &config) {
|
|
|
+ assert(config.contains("watch_list"));
|
|
|
for (auto &watch_config: config["watch_list"]) {
|
|
|
assert(watch_config.contains("target"));
|
|
|
assert(watch_config["target"].is_string());
|
|
|
assert(watch_config.contains("observer"));
|
|
|
assert(watch_config["observer"].is_string());
|
|
|
+ assert(watch_config.contains("transform_name"));
|
|
|
+ assert(watch_config["transform_name"].is_string());
|
|
|
auto target_name = watch_config["target"].get<std::string>();
|
|
|
auto observer_name = watch_config["observer"].get<std::string>();
|
|
|
auto target_index = nodes.to_index_by_name(target_name);
|
|
|
auto observer_index = nodes.to_index_by_name(observer_name);
|
|
|
- auto watch_signal = create_watch(observer_index, target_index);
|
|
|
- auto watch_name = fmt::format("track_{}_{}", observer_name, target_name);
|
|
|
- if (watch_config.contains("name")) {
|
|
|
- assert(watch_config["name"].is_string());
|
|
|
- watch_name = watch_config["name"].get<std::string>();
|
|
|
- }
|
|
|
-// get_manager().register_signal(q_this, watch_name, *watch_signal);
|
|
|
+ auto transform_obj_name = watch_config["transform_name"].get<std::string>();
|
|
|
+ auto transform_obj_index = get_manager().
|
|
|
+ register_global_obj<transform_obj>(transform_obj_name);
|
|
|
+ create_worker(observer_index, target_index, transform_obj_index);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
transform_tree::transform_tree()
|
|
|
- : pimpl(std::make_unique<impl>()) {
|
|
|
- pimpl->q_this = this;
|
|
|
- }
|
|
|
+ : pimpl(std::make_unique<impl>()) {}
|
|
|
|
|
|
- std::tuple<bool, transform_tree::trans_type>
|
|
|
- transform_tree::query_transform(index_type observer, index_type target) {
|
|
|
- return pimpl->query_transform(observer, target);
|
|
|
+ transform_tree::~transform_tree() = default;
|
|
|
+
|
|
|
+ boost::asio::awaitable<bool> transform_tree::on_init(const nlohmann::json &config) {
|
|
|
+ pimpl->build_tree(config);
|
|
|
+ co_return true;
|
|
|
}
|
|
|
|
|
|
- void transform_tree::load_construct_config(const nlohmann::json &config) {
|
|
|
- pimpl->load_nodes(config);
|
|
|
- pimpl->load_watches(config);
|
|
|
+ boost::asio::awaitable<bool> transform_tree::on_start(const nlohmann::json &config) {
|
|
|
+ pimpl->config_watcher(config);
|
|
|
+ co_return true;
|
|
|
}
|
|
|
|
|
|
- transform_tree::index_type transform_tree::get_index_by_name(const std::string &name) {
|
|
|
- return pimpl->nodes.to_index_by_name(name);
|
|
|
+ boost::asio::awaitable<void> transform_tree::on_stop() {
|
|
|
+ co_await pimpl->stop_workers();
|
|
|
+ co_return;
|
|
|
}
|
|
|
|
|
|
- transform_tree::~transform_tree() = default;
|
|
|
+ boost::asio::awaitable<void> transform_tree::on_reset() {
|
|
|
+ pimpl->nodes.clear();
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
|
|
|
}
|