diff --git a/components/structures/CMakeLists.txt b/components/structures/CMakeLists.txt index fbbddc846b25e50493300c31a7ed57b87bc2bcbf..5df4a3870d0caccbef30d9d622dcf6268cf8e83d 100644 --- a/components/structures/CMakeLists.txt +++ b/components/structures/CMakeLists.txt @@ -1,10 +1,10 @@ -add_library(ftldata INTERFACE) +add_library(ftldata STATIC ./src/new_frame.cpp) -target_include_directories(ftldata INTERFACE +target_include_directories(ftldata PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_link_libraries(ftldata INTERFACE ftlcommon Eigen3::Eigen ftlcodecs) +target_link_libraries(ftldata ftlcommon Eigen3::Eigen ftlcodecs) -#add_subdirectory(test) +add_subdirectory(test) diff --git a/components/structures/include/ftl/data/new_frame.hpp b/components/structures/include/ftl/data/new_frame.hpp new file mode 100644 index 0000000000000000000000000000000000000000..caec76a6d150e630a584b097426707cc67501a55 --- /dev/null +++ b/components/structures/include/ftl/data/new_frame.hpp @@ -0,0 +1,147 @@ +#ifndef _FTL_DATA_NEWFRAME_HPP_ +#define _FTL_DATA_NEWFRAME_HPP_ + +#include <map> +#include <unordered_set> +#include <any> +#include <optional> +#include <list> +#include <unordered_map> +#include <ftl/codecs/channels.hpp> +#include <ftl/exception.hpp> + +namespace ftl { +namespace data { + +class Frame { + public: + uint32_t id=0; + int64_t timestamp=0; + + public: + Frame() : parent_(nullptr) {}; + explicit Frame(Frame *parent) : parent_(parent) {}; + ~Frame() { flush(); }; + + inline bool has(ftl::codecs::Channel c) { + return data_.find(c) != data_.end() || (parent_ && parent_->has(c)); + } + + inline bool changed(ftl::codecs::Channel c) { + return changed_.find(c) != changed_.end(); + } + + template <typename T> + bool isType(ftl::codecs::Channel c); + + template <typename T> + const T *get(ftl::codecs::Channel c) const; + + template <typename T> + T *getMutable(ftl::codecs::Channel c); + + inline void touch(ftl::codecs::Channel c) { + changed_.emplace(c); + } + + inline void untouch(ftl::codecs::Channel c) { + changed_.erase(c); + } + + template <typename T> + void create(ftl::codecs::Channel c, const T &value); + + template <typename T> + void create(ftl::codecs::Channel c); + + template <typename T> + void push(ftl::codecs::Channel c, const T &v); + + template <typename T> + void set(ftl::codecs::Channel c, const T &v); + + inline void on(ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + if (parent_) parent_->on(c, cb); + else triggers_[c].push_back(cb); // TODO: Find better way to enable removal + } + + inline void on(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + any_triggers_.push_back(cb); + } + + /** Send changes back through origin stream. */ + bool flush(); + + // onBeginFlush + // onEndFlush + // onError + + private: + std::map<ftl::codecs::Channel, std::any> data_; + std::unordered_set<ftl::codecs::Channel> changed_; + std::unordered_map<ftl::codecs::Channel, std::list<std::function<bool(Frame&,ftl::codecs::Channel)>>> triggers_; + std::list<std::function<bool(Frame&,ftl::codecs::Channel)>> any_triggers_; + Frame *parent_; +}; + +} +} + +// ==== Implementations ======================================================== + +template <typename T> +bool ftl::data::Frame::isType(ftl::codecs::Channel c) { + auto i = data_.find(c); + if (i != data_.end()) { + return typeid(T) == i->second.type(); + } else return false; +} + +template <typename T> +const T *ftl::data::Frame::get(ftl::codecs::Channel c) const { + auto i = data_.find(c); + if (i != data_.end()) { + return std::any_cast<T>(&i->second); + } else if (parent_) { + return parent_->get<T>(c); + } else return nullptr; +} + +template <typename T> +void ftl::data::Frame::create(ftl::codecs::Channel c, const T &value) { + data_[c] = value; + touch(c); +} + +template <typename T> +void ftl::data::Frame::create(ftl::codecs::Channel c) { + if (!isType<T>(c)) data_[c] = T{}; + touch(c); +} + +template <typename T> +void ftl::data::Frame::push(ftl::codecs::Channel c, const T &v) { + auto i = data_.find(c); + if (i != data_.end()) { + auto *p = std::any_cast<std::vector<T>>(&i->second); + p->push_back(v); + } else { + throw FTL_Error("Push on missing channel (" << static_cast<unsigned int>(c) << ")"); + } + touch(c); +} + +template <typename T> +void ftl::data::Frame::set(ftl::codecs::Channel c, const T &v) { + auto i = data_.find(c); + if (i != data_.end()) { + i->second = v; + } else if (parent_ && parent_->isType<T>(c)) { + create<T>(c, v); + } else { + throw FTL_Error("Set on missing channel (" << static_cast<unsigned int>(c) << ")"); + } + touch(c); +} + +#endif \ No newline at end of file diff --git a/components/structures/src/new_frame.cpp b/components/structures/src/new_frame.cpp new file mode 100644 index 0000000000000000000000000000000000000000..52f40ad7e21600050da6134141e0e899d0184697 --- /dev/null +++ b/components/structures/src/new_frame.cpp @@ -0,0 +1,32 @@ +#include <ftl/data/new_frame.hpp> + +using ftl::data::Frame; + +#define LOGURU_REPLACE_GLOG 1 +#include <loguru.hpp> + +bool Frame::flush() { + if (parent_) { + for (auto c : changed_) { + parent_->changed_.emplace(c); + parent_->data_[c] = std::move(data_[c]); + data_.erase(c); + } + parent_->flush(); + } else { + for (auto c : changed_) { + auto i = triggers_.find(c); + if (i != triggers_.end()) { + for (auto f : i->second) { + try { + f(*this, c); + } catch (const std::exception &e) { + LOG(ERROR) << "Exception in frame flush: " << e.what(); + } + } + } + } + } + changed_.clear(); + return true; +} diff --git a/components/structures/test/CMakeLists.txt b/components/structures/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..155634bc4eb4fda07ad55405d1ceabd7d5293aa6 --- /dev/null +++ b/components/structures/test/CMakeLists.txt @@ -0,0 +1,9 @@ +### Frame Unit ################################################################# +add_executable(nframe_unit + $<TARGET_OBJECTS:CatchTest> + ./frame_unit.cpp + ../src/new_frame.cpp +) +target_include_directories(nframe_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(nframe_unit + ftlcommon ftlcodecs) \ No newline at end of file diff --git a/components/structures/test/frame_unit.cpp b/components/structures/test/frame_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a866b8e440228f84ec060999995ba0db09d05244 --- /dev/null +++ b/components/structures/test/frame_unit.cpp @@ -0,0 +1,243 @@ +#include "catch.hpp" + +#include <ftl/data/new_frame.hpp> + + +using ftl::data::Frame; +using ftl::codecs::Channel; + +TEST_CASE("ftl::data::Frame create get", "[Frame]") { + SECTION("write and read integers") { + Frame f; + f.create<int>(Channel::Pose, 55); + + auto x = f.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 55 ); + } + + SECTION("write and read floats") { + Frame f; + f.create<float>(Channel::Pose, 44.0f); + + auto x = f.get<float>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 44.0f ); + } + + SECTION("write and read structures") { + struct Test { + int a=44; + float b=33.0f; + }; + Frame f; + f.create<Test>(Channel::Pose, {}); + + auto x = f.get<Test>(Channel::Pose); + REQUIRE( x ); + REQUIRE( x->a == 44 ); + REQUIRE( x->b == 33.0f ); + } + + SECTION("write and read fail") { + struct Test { + int a=44; + float b=33.0f; + }; + Frame f; + f.create<Test>(Channel::Pose, {}); + + auto x = f.get<int>(Channel::Pose); + REQUIRE( !x ); + } +} + +TEST_CASE("ftl::data::Frame isType", "[Frame]") { + SECTION("is int type") { + Frame f; + f.create<int>(Channel::Pose, 55); + + REQUIRE( f.isType<int>(Channel::Pose) ); + REQUIRE( !f.isType<float>(Channel::Pose) ); + } + + SECTION("is struct type") { + struct Test { + int a; int b; + }; + + Frame f; + f.create<Test>(Channel::Pose, {3,4}); + + REQUIRE( f.isType<Test>(Channel::Pose) ); + REQUIRE( !f.isType<float>(Channel::Pose) ); + } + + SECTION("missing") { + Frame f; + + REQUIRE( !f.isType<float>(Channel::Pose) ); + } +} + +TEST_CASE("ftl::data::Frame changed", "[Frame]") { + SECTION("change on create") { + Frame f; + + REQUIRE( !f.changed(Channel::Pose) ); + f.create<int>(Channel::Pose, 55); + REQUIRE( f.changed(Channel::Pose) ); + } + + SECTION("no change on untouch") { + Frame f; + + f.create<int>(Channel::Pose, 55); + REQUIRE( f.changed(Channel::Pose) ); + f.untouch(Channel::Pose); + REQUIRE( !f.changed(Channel::Pose) ); + } +} + +TEST_CASE("ftl::data::Frame create", "[Frame]") { + SECTION("same value on create") { + Frame f; + + f.create<int>(Channel::Pose, 55); + auto x = f.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 55 ); + + f.create<int>(Channel::Pose); + auto y = f.get<int>(Channel::Pose); + REQUIRE( y ); + REQUIRE( *y == 55 ); + + REQUIRE( x == y ); + } + + SECTION("change of type") { + Frame f; + + f.create<int>(Channel::Pose, 55); + auto x = f.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 55 ); + + f.create<float>(Channel::Pose); + auto y = f.get<float>(Channel::Pose); + REQUIRE( y ); + REQUIRE( *y == 0.0f ); + } +} + +TEST_CASE("ftl::data::Frame use of parent", "[Frame]") { + SECTION("get from parent") { + Frame p; + Frame f(&p); + + p.create<int>(Channel::Pose, 55); + + auto x = f.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 55 ); + + auto y = p.get<int>(Channel::Pose); + REQUIRE( x == y ); + } + + SECTION("has from parent") { + Frame p; + Frame f(&p); + + p.create<int>(Channel::Pose, 55); + REQUIRE( f.has(Channel::Pose) ); + } + + SECTION("no change in parent") { + Frame p; + Frame f(&p); + + p.create<int>(Channel::Pose, 55); + p.untouch(Channel::Pose); + + REQUIRE( !f.changed(Channel::Pose) ); + REQUIRE( !p.changed(Channel::Pose) ); + + f.set<int>(Channel::Pose, 66); + + REQUIRE( f.changed(Channel::Pose) ); + REQUIRE( !p.changed(Channel::Pose) ); + + auto x = f.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 66 ); + + auto y = p.get<int>(Channel::Pose); + REQUIRE( y ); + REQUIRE( *y == 55 ); + } +} + +TEST_CASE("ftl::data::Frame flush", "[Frame]") { + SECTION("event on flush") { + Frame f; + + int event = 0; + f.on(Channel::Pose, [&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.create<int>(Channel::Pose, 55); + REQUIRE( event == 0 ); + + f.flush(); + REQUIRE( event == 1 ); + } + + SECTION("parent event on flush") { + Frame p; + Frame f(&p); + + int event = 0; + p.on(Channel::Pose, [&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.create<int>(Channel::Pose, 55); + REQUIRE( event == 0 ); + + f.flush(); + REQUIRE( event == 1 ); + } + + SECTION("parent change on flush") { + Frame p; + Frame f(&p); + + p.create<int>(Channel::Pose, 55); + p.flush(); + + f.set<int>(Channel::Pose, 66); + auto x = p.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 55 ); + + f.flush(); + x = p.get<int>(Channel::Pose); + REQUIRE( x ); + REQUIRE( *x == 66 ); + } + + SECTION("untouched on flush") { + Frame f; + + f.create<int>(Channel::Pose, 55); + REQUIRE( f.changed(Channel::Pose) ); + + f.flush(); + REQUIRE( !f.changed(Channel::Pose) ); + } +}