From cab7ba8880a58d955801373ed9209d8a13441764 Mon Sep 17 00:00:00 2001 From: Nicolas Pope <nicolas.pope@utu.fi> Date: Fri, 12 Jun 2020 13:42:47 +0300 Subject: [PATCH] New Frame Class --- CMakeLists.txt | 4 + components/common/cpp/include/ftl/handle.hpp | 16 +- components/common/cpp/test/handle_unit.cpp | 16 + .../streams/include/ftl/streams/feed.hpp | 33 + components/structures/CMakeLists.txt | 4 +- .../structures/include/ftl/data/channels.hpp | 97 ++ .../structures/include/ftl/data/framepool.hpp | 43 + .../structures/include/ftl/data/new_frame.hpp | 435 +++-- components/structures/src/new_frame.cpp | 279 +++- components/structures/src/pool.cpp | 75 + components/structures/test/CMakeLists.txt | 18 +- .../structures/test/frame_example_1.cpp | 135 ++ components/structures/test/frame_unit.cpp | 1405 +++++++++++++++-- components/structures/test/pool_unit.cpp | 120 ++ lib/libsgm/src/CMakeLists.txt | 2 + lib/libstereo/CMakeLists.txt | 1 + 16 files changed, 2354 insertions(+), 329 deletions(-) create mode 100644 components/streams/include/ftl/streams/feed.hpp create mode 100644 components/structures/include/ftl/data/channels.hpp create mode 100644 components/structures/include/ftl/data/framepool.hpp create mode 100644 components/structures/src/pool.cpp create mode 100644 components/structures/test/frame_example_1.cpp create mode 100644 components/structures/test/pool_unit.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 96fb65323..4df38ff5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,10 @@ MACRO( VERSION_STR_TO_INTS major minor patch version ) ENDMACRO( VERSION_STR_TO_INTS ) +if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CUDA_HOST_COMPILER gcc-7) +endif() + find_package( OpenCV REQUIRED COMPONENTS core imgproc highgui cudaimgproc calib3d imgcodecs videoio aruco cudaarithm cudastereo cudaoptflow face tracking quality xfeatures2d) find_package( Threads REQUIRED ) find_package( URIParser REQUIRED ) diff --git a/components/common/cpp/include/ftl/handle.hpp b/components/common/cpp/include/ftl/handle.hpp index 7a2348d5b..d7e2f8089 100644 --- a/components/common/cpp/include/ftl/handle.hpp +++ b/components/common/cpp/include/ftl/handle.hpp @@ -72,7 +72,7 @@ struct Handler : BaseHandler { * Add a new callback function. It returns a `Handle` object that must * remain in scope, the destructor of the `Handle` will remove the callback. */ - Handle on(const std::function<bool(ARGS...)> &f) { + [[nodiscard]] Handle on(const std::function<bool(ARGS...)> &f) { std::unique_lock<std::mutex> lk(mutex_); int id = id_++; callbacks_[id] = f; @@ -87,13 +87,15 @@ struct Handler : BaseHandler { */ void trigger(ARGS ...args) { std::unique_lock<std::mutex> lk(mutex_); - try { - for (auto &f : callbacks_) { - f.second(std::forward<ARGS...>(args...)); + //try { + for (auto i=callbacks_.begin(); i!=callbacks_.end(); ) { + bool keep = i->second(std::forward<ARGS>(args)...); + if (!keep) i = callbacks_.erase(i); + else ++i; } - } catch (const std::exception &e) { - LOG(ERROR) << "Exception in callback: " << e.what(); - } + //} catch (const std::exception &e) { + // LOG(ERROR) << "Exception in callback: " << e.what(); + //} } /** diff --git a/components/common/cpp/test/handle_unit.cpp b/components/common/cpp/test/handle_unit.cpp index 1c1c27d61..313967597 100644 --- a/components/common/cpp/test/handle_unit.cpp +++ b/components/common/cpp/test/handle_unit.cpp @@ -23,6 +23,22 @@ TEST_CASE( "Handle release on cancel" ) { REQUIRE(calls == 5); } +TEST_CASE( "Handle release on false return" ) { + Handler<int> handler; + + int calls = 0; + + auto h = handler.on([&calls](int i) { + calls += i; + return false; + }); + + handler.trigger(5); + REQUIRE(calls == 5); + handler.trigger(5); + REQUIRE(calls == 5); +} + TEST_CASE( "Handle multiple triggers" ) { Handler<int> handler; diff --git a/components/streams/include/ftl/streams/feed.hpp b/components/streams/include/ftl/streams/feed.hpp new file mode 100644 index 000000000..890eb64e5 --- /dev/null +++ b/components/streams/include/ftl/streams/feed.hpp @@ -0,0 +1,33 @@ +#ifndef _FTL_STREAMS_FEED_HPP_ +#define _FTL_STREAMS_FEED_HPP_ + +#include <ftl/configurable.hpp> + +namespace ftl { +namespace streams { + +using FrameSetHandler = std::function<bool(ftl::data::FrameSet&)>; + +class Feed : public ftl::Configurable { + public: + + void addStream(ftl::streams::Stream *s); + + ftl::Handler onFrameSet(const FrameSetHandler &fsh); + + uint32_t allocateFrameId(); + + void createFrame(uint32_t id, ftl::data::Frame &f); + + void createFrameSet(uint32_t id, int size, ftl::data::FrameSet &fs); + + private: + ftl::streams::Receiver *receiver_; + ftl::streams::Sender *sender_; + std::unordered_map<uint32_t, ftl::data::Session> stores_; +}; + +} +} + +#endif \ No newline at end of file diff --git a/components/structures/CMakeLists.txt b/components/structures/CMakeLists.txt index 5df4a3870..b47288977 100644 --- a/components/structures/CMakeLists.txt +++ b/components/structures/CMakeLists.txt @@ -1,10 +1,12 @@ -add_library(ftldata STATIC ./src/new_frame.cpp) +add_library(ftldata ./src/new_frame.cpp ./src/pool.cpp) target_include_directories(ftldata PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(ftldata ftlcommon Eigen3::Eigen ftlcodecs) +if (BUILD_TESTS) add_subdirectory(test) +endif() diff --git a/components/structures/include/ftl/data/channels.hpp b/components/structures/include/ftl/data/channels.hpp new file mode 100644 index 000000000..341759b13 --- /dev/null +++ b/components/structures/include/ftl/data/channels.hpp @@ -0,0 +1,97 @@ +#ifndef _FTL_DATA_CHANNELS_HPP_ +#define _FTL_DATA_CHANNELS_HPP_ + +#include <string> +#include <ftl/codecs/channels.hpp> +#include <ftl/exception.hpp> + +namespace ftl { +namespace data { + + +/** Kind of channel in terms of data persistence */ +enum class StorageMode { + PERSISTENT, // Most recent value, even from previous fram + TRANSIENT, // Only most recent value since last frame + AGGREGATE // All changes since last frame +}; + +/** If a channel has changed, what is the current status of that change. */ +enum class ChangeType { + UNCHANGED, + LOCAL, // Explicit local modification occurred + FOREIGN, // Received externally, to be forwarded + COMPLETED // Received externally, not to be forwarded +}; + +/** Current status of the data contained within a channel. */ +enum class ChannelStatus { + INVALID, // Any data is stale and should not be referenced + VALID, // Contains currently valid data + FLUSHED, // Has already been transmitted, now read-only + DISPATCHED // Externally received, can't be flushed but can be modified locally +}; + +/* Internal structure for channel configurations. */ +struct ChannelConfig { + std::string name; + StorageMode mode; + size_t type_id; +}; + +/** + * Add a channel configuration to the registry. By default channels are not + * in the registry and this means they have no name or specified type. Non + * registered channels can still be used but no runtime checks are performed. + */ +void registerChannel(ftl::codecs::Channel, const ChannelConfig &config); + +/** Used by unit tests. */ +void clearRegistry(); + +/** + * Check if channel is marked as persistent storage in the registry. + */ +bool isPersistent(ftl::codecs::Channel); + +bool isAggregate(ftl::codecs::Channel); + +/** + * Get channel type hash_code as from `std::type_info::hash_code()`. This + * returns 0 if not registered or registered as allowing any time, 0 means + * accept any type. + */ +size_t getChannelType(ftl::codecs::Channel); + +template <typename T> +void verifyChannelType(ftl::codecs::Channel c) { + size_t t = getChannelType(c); + if (t > 0 && t != typeid(T).hash_code()) throw FTL_Error("Incorrect type for channel " << static_cast<unsigned int>(c)); +} + +/** + * Get the registered string name for channel, or an empty string if no name. + */ +std::string getChannelName(ftl::codecs::Channel); + +/** Unsupported */ +ftl::codecs::Channel getChannelByName(const std::string &name); + +/** + * Helper to register a channel using a template specified type. + */ +template <typename T> +void make_channel(ftl::codecs::Channel c, const std::string &name, StorageMode mode) { + // TODO: Generate packer + unpacker? + registerChannel(c, {name, mode, typeid(T).hash_code()}); +} + +template <> +inline void make_channel<void>(ftl::codecs::Channel c, const std::string &name, StorageMode mode) { + registerChannel(c, {name, mode, 0}); +} + +} +} + +#endif diff --git a/components/structures/include/ftl/data/framepool.hpp b/components/structures/include/ftl/data/framepool.hpp new file mode 100644 index 000000000..204965ff7 --- /dev/null +++ b/components/structures/include/ftl/data/framepool.hpp @@ -0,0 +1,43 @@ +#ifndef _FTL_DATA_FRAMEPOOL_HPP_ +#define _FTL_DATA_FRAMEPOOL_HPP_ + +#include <ftl/data/new_frame.hpp> +#include <list> +#include <unordered_map> + +namespace ftl { +namespace data { + +class Pool { + public: + explicit Pool(size_t min_n, size_t max_n); + ~Pool(); + + ftl::data::Frame allocate(uint32_t id, int64_t timestamp); + void release(Frame &f); + + ftl::data::Session &session(uint32_t id); + + size_t size(uint32_t id); + + size_t size(); + + private: + struct PoolData { + std::list<ftl::data::Frame*> pool; + ftl::data::Session session; + int64_t last_timestamp; + }; + + std::unordered_map<uint32_t, PoolData> pool_; + size_t min_n_; + size_t max_n_; + size_t ideal_n_; + + PoolData &_getPool(uint32_t); +}; + +} +} + +#endif \ No newline at end of file diff --git a/components/structures/include/ftl/data/new_frame.hpp b/components/structures/include/ftl/data/new_frame.hpp index 963e91b5f..5a45662de 100644 --- a/components/structures/include/ftl/data/new_frame.hpp +++ b/components/structures/include/ftl/data/new_frame.hpp @@ -8,55 +8,79 @@ #include <list> #include <unordered_map> #include <ftl/codecs/channels.hpp> +#include <ftl/data/channels.hpp> #include <ftl/exception.hpp> +#include <ftl/handle.hpp> + +template<typename T> struct is_list : public std::false_type {}; + +template<typename T> +struct is_list<std::list<T>> : public std::true_type {}; namespace ftl { +namespace streams { class Feed; } namespace data { class Session; +class Pool; -/** Kind of channel in terms of data persistence */ -enum class ChannelMode { - PERSISTENT, // Most recent value, even from previous fram - IMMEDIATE, // Only most recent value since last frame - SEQUENCE // All changes since last frame -}; - -struct ChannelConfig { - std::string name; - ChannelMode mode; - size_t type_id; +enum FrameStatus { + CREATED, // Initial state, before store + STORED, // Changed to this after call to `store` + FLUSHED, // Changed to this after call to `flush` + RELEASED // Destroyed or moved }; +/** + * Helper class to enable aggregation of aggregate channels. + */ template <typename T> -ChannelConfig make_channel(const std::string &name, ChannelMode mode) { - // TODO: Generate packer + unpacker? - return {name, mode, typeid(T).hash_code()}; -} +struct Aggregator { + T &list; + bool aggregate=true; + + Aggregator &operator=(const T &l) { + if (aggregate) list.insert(list.end(), l.begin(), l.end()); + else list = l; + return *this; + } -//template <> -//ChannelConfig make_channel<void>(const std::string &name, ChannelMode mode) { -// return {name, mode, 0}; -//} + Aggregator &operator=(T &&l) { + if (aggregate) list.splice(list.end(), l, l.begin(), l.end()); + else list = std::move(l); + return *this; + } + + operator T() { return list; } + operator T() const { return list; } +}; class Frame { + friend class Session; + friend class ftl::data::Pool; + friend class ftl::streams::Feed; + + private: + // Only Feed class should construct + Frame(Pool *ppool, Session *parent, uint32_t pid, int64_t ts) : timestamp_(ts), id(pid), pool_(ppool), parent_(parent), status_(FrameStatus::CREATED) {}; + int64_t timestamp_=0; + public: - uint32_t id=0; - int64_t timestamp=0; + const uint32_t id=0; + + inline int64_t timestamp() const { return timestamp_; } public: - Frame() : parent_(nullptr) {}; - explicit Frame(Session *parent) : parent_(parent) {}; - ~Frame() { flush(); }; + Frame()=delete; + + ~Frame(); Frame(Frame &&f) { - f.swapTo(*this); - f.reset(); + f.moveTo(*this); } Frame &operator=(Frame &&f) { - f.swapTo(*this); - f.reset(); + f.moveTo(*this); return *this; } @@ -64,18 +88,34 @@ class Frame { Frame(const Frame &)=delete; Frame &operator=(const Frame &)=delete; - inline bool has(ftl::codecs::Channel c); + inline FrameStatus status() const { return status_; } + + inline size_t size() const { return data_.size(); } + + bool has(ftl::codecs::Channel c) const; + + inline bool hasOwn(ftl::codecs::Channel c) const; + + inline bool changed(ftl::codecs::Channel c) const; + + inline bool readonly(ftl::codecs::Channel c) const; - inline bool changed(ftl::codecs::Channel c); + inline bool flushed(ftl::codecs::Channel c) const; - inline const std::unordered_set<ftl::codecs::Channel> &changed() const { return changed_; } + inline ftl::data::ChangeType getChangeType(ftl::codecs::Channel c) const; + + inline const std::unordered_map<ftl::codecs::Channel, ChangeType> &changed() const { return changed_; } template <typename T> - bool isType(ftl::codecs::Channel c); + bool isType(ftl::codecs::Channel c) const; template <typename T> const T &get(ftl::codecs::Channel c) const; + const std::any &getAny(ftl::codecs::Channel c) const; + + std::any &getAnyMutable(ftl::codecs::Channel c); + template <typename T> const T *getPtr(ftl::codecs::Channel c) const noexcept; @@ -83,47 +123,109 @@ class Frame { T *getMutable(ftl::codecs::Channel c); inline void touch(ftl::codecs::Channel c) { - changed_.emplace(c); + changed_[c] = ChangeType::LOCAL; + } + + inline void touch(ftl::codecs::Channel c, ChangeType type) { + changed_[c] = type; } inline void untouch(ftl::codecs::Channel c) { changed_.erase(c); } - template <typename T> - T &create(ftl::codecs::Channel c, const T &value); + template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> + T &create(ftl::codecs::Channel c); + + template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> + ftl::data::Aggregator<T> create(ftl::codecs::Channel c); + + /** + * Creates a channel data entry with a forced change status. This also + * changes the channel status to `DISPATCHED`. If the storage mode is + * `persistent` this adds to session store instead of local frame store, + * although the change status is added to the local frame. + * + * To be used by receive, no one else. + */ + template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> + T &createChange(ftl::codecs::Channel c, ftl::data::ChangeType t); + + template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> + ftl::data::Aggregator<T> createChange(ftl::codecs::Channel c, ftl::data::ChangeType t); template <typename T> - T &create(ftl::codecs::Channel c); + T &createChange(ftl::codecs::Channel c, ftl::data::ChangeType t, std::vector<uint8_t> &data); + + const std::vector<uint8_t> &getEncoded(ftl::codecs::Channel c) const; template <typename T, typename ...ARGS> T &emplace(ftl::codecs::Channel, ARGS...); - template <typename T> - void push(ftl::codecs::Channel c, const T &v); + template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> + T &set(ftl::codecs::Channel c); - template <typename T> - void set(ftl::codecs::Channel c, const T &v); + template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> + ftl::data::Aggregator<T> set(ftl::codecs::Channel c); + + /** + * Will remove a channel by changing its status and will not remove data. + */ + void remove(ftl::codecs::Channel); + + /** + * Will remove a channel and destroy all data associated with it. + */ + void hardRemove(ftl::codecs::Channel); - inline void on(ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + inline ftl::Handle onChange(ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); - inline void on(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + inline ftl::Handle onChange(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + inline ftl::Handle onFlush(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + + /** + * Merge the given frame parameter into this frame. It is a move operation + * on a per channel basis. + */ void merge(Frame &); - void swapTo(Frame &); + void moveTo(Frame &); void swapChanged(Frame &); void swapChannel(ftl::codecs::Channel, Frame &); - inline void reset() { changed_.clear(); } + /** + * Discard all change status without removing the data. + */ + inline void resetChanges() { changed_.clear(); } + + /** + * Clears all state to an empty condition without releasing memory. + */ + void reset(); + + /** + * Deletes all memory and resets to starting condition. This should not + * be used, instead use `release()` which will save the memory into a pool + * rather than deleting it completely. + */ + void hardReset(); - void clear(); + /** + * Free memory into the memory pool. This also implicitly resets. + */ + void release(); /** Send changes back through origin stream. */ bool flush(); + bool flush(ftl::codecs::Channel c); + + /** Copy persistent changes to session. To be called before dispatch. */ + void store(); + inline auto begin() const { return data_.begin(); } inline auto end() const { return data_.end(); } @@ -131,124 +233,245 @@ class Frame { // onEndFlush // onError - static void registerChannel(ftl::codecs::Channel, const ChannelConfig &config); - static void clearRegistry(); + inline MUTEX &mutex(); + + protected: + std::any &createAnyChange(ftl::codecs::Channel c, ftl::data::ChangeType t); + + std::any &createAnyChange(ftl::codecs::Channel c, ftl::data::ChangeType t, std::vector<uint8_t> &data); - static bool isPersistent(ftl::codecs::Channel); - static size_t getChannelType(ftl::codecs::Channel); - static std::string getChannelName(ftl::codecs::Channel); - static ftl::codecs::Channel getChannelByName(const std::string &name); + std::any &createAny(ftl::codecs::Channel c); 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_; + struct ChannelData { + ChannelStatus status=ChannelStatus::INVALID; + std::any data; + std::vector<uint8_t> encoded={}; + }; + + ChannelData &_getData(ftl::codecs::Channel); + const ChannelData &_getData(ftl::codecs::Channel) const; + + std::map<ftl::codecs::Channel, ChannelData> data_; + std::unordered_map<ftl::codecs::Channel, ChangeType> changed_; + Pool *pool_; Session *parent_; + FrameStatus status_; + + inline void restart(int64_t ts) { + timestamp_ = ts; + status_ = FrameStatus::CREATED; + } }; -class Session : public Frame {}; +class Session : public Frame { + friend class Frame; + + public: + Session() : Frame(nullptr, nullptr,0,0) { + status_ = FrameStatus::STORED; + } + + ~Session() { + // Sessions don't get memory pooled. + status_ = FrameStatus::RELEASED; + } + + ftl::Handle onChange(uint32_t id, ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + + ftl::Handle onChange(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + + ftl::Handle onFlush(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + + void notifyChanges(Frame &f); + + void flush(Frame &f); + + void flush(Frame &f, ftl::codecs::Channel c); + + inline MUTEX &mutex() { return mutex_; } + + private: + std::unordered_map<uint64_t, ftl::Handler<Frame&,ftl::codecs::Channel>> change_channel_; + ftl::Handler<Frame&,ftl::codecs::Channel> change_; + ftl::Handler<Frame&,ftl::codecs::Channel> flush_; + + MUTEX mutex_; +}; } } // ==== Implementations ======================================================== -bool ftl::data::Frame::has(ftl::codecs::Channel c) { - return data_.find(c) != data_.end() || (parent_ && parent_->has(c)); +MUTEX &ftl::data::Frame::mutex() { return parent_->mutex(); } + +bool ftl::data::Frame::hasOwn(ftl::codecs::Channel c) const { + const auto &i = data_.find(c); + return (i != data_.end() && i->second.status != ftl::data::ChannelStatus::INVALID); } -bool ftl::data::Frame::changed(ftl::codecs::Channel c) { +bool ftl::data::Frame::changed(ftl::codecs::Channel c) const { return changed_.find(c) != changed_.end(); } -void ftl::data::Frame::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 +ftl::data::ChangeType ftl::data::Frame::getChangeType(ftl::codecs::Channel c) const { + const auto &i = changed_.find(c); + return (i == changed_.end()) ? ftl::data::ChangeType::UNCHANGED : i->second; +} + +bool ftl::data::Frame::flushed(ftl::codecs::Channel c) const { + const auto &d = _getData(c); + return d.status == ChannelStatus::FLUSHED; +} + +bool ftl::data::Frame::readonly(ftl::codecs::Channel c) const { + return flushed(c); } -void ftl::data::Frame::on(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { - any_triggers_.push_back(cb); +ftl::Handle ftl::data::Frame::onChange(ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + return parent_->onChange(id, c, cb); +} + +ftl::Handle ftl::data::Frame::onChange(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + return parent_->onChange(cb); +} + +ftl::Handle ftl::data::Frame::onFlush(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + return parent_->onFlush(cb); } 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; +bool ftl::data::Frame::isType(ftl::codecs::Channel c) const { + const auto &i = data_.find(c); + if (i != data_.end() && i->second.status != ftl::data::ChannelStatus::INVALID) { + return typeid(T) == i->second.data.type(); + } else { + return (parent_ && parent_->isType<T>(c)); + } } template <typename T> const T *ftl::data::Frame::getPtr(ftl::codecs::Channel c) const noexcept { - auto i = data_.find(c); - if (i != data_.end()) { - return std::any_cast<T>(&i->second); - } else if (parent_) { - return parent_->getPtr<T>(c); + const auto &d = _getData(c); + if (d.status != ftl::data::ChannelStatus::INVALID) { + return std::any_cast<T>(&d.data); } else return nullptr; } template <typename T> const T &ftl::data::Frame::get(ftl::codecs::Channel c) const { - auto i = data_.find(c); - if (i != data_.end()) { - auto *p = std::any_cast<T>(&i->second); + const auto &d = _getData(c); + if (d.status != ftl::data::ChannelStatus::INVALID) { + auto *p = std::any_cast<T>(&d.data); if (!p) throw FTL_Error("'get' wrong type for channel (" << static_cast<unsigned int>(c) << ")"); return *p; - } else if (parent_) { - return parent_->get<T>(c); - } else throw FTL_Error("'get' on missing channel (" << static_cast<unsigned int>(c) << ")"); + } else throw FTL_Error("Missing channel (" << static_cast<unsigned int>(c) << ")"); } -template <typename T> -T &ftl::data::Frame::create(ftl::codecs::Channel c, const T &value) { - touch(c); - size_t t = getChannelType(c); - if (t > 0 && t != typeid(T).hash_code()) throw FTL_Error("Incorrect type for channel " << static_cast<unsigned int>(c)); - auto &d = data_[c]; - d = value; - return *std::any_cast<T>(&d); +// Non-list version +template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> +T &ftl::data::Frame::create(ftl::codecs::Channel c) { + if (isAggregate(c)) throw FTL_Error("Aggregate channels must be of list type"); + + ftl::data::verifyChannelType<T>(c); + + std::any &a = createAny(c); + if (!isType<T>(c)) return a.emplace<T>(); + else return *std::any_cast<T>(&a); +} + +// List version +template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> +ftl::data::Aggregator<T> ftl::data::Frame::create(ftl::codecs::Channel c) { + ftl::data::verifyChannelType<T>(c); + + std::any &a = createAny(c); + if (!isType<T>(c)) a.emplace<T>(); + return ftl::data::Aggregator<T>{*std::any_cast<T>(&a), isAggregate(c)}; } template <typename T> -T &ftl::data::Frame::create(ftl::codecs::Channel c) { - touch(c); - size_t t = getChannelType(c); - if (t > 0 && t != typeid(T).hash_code()) throw FTL_Error("Incorrect type for channel " << static_cast<unsigned int>(c)); - if (!isType<T>(c)) return data_[c].emplace<T>(); - else return *std::any_cast<T>(&data_[c]); +T &ftl::data::Frame::createChange(ftl::codecs::Channel c, ftl::data::ChangeType type, std::vector<uint8_t> &data) { + if (!bool(is_list<T>{}) && isAggregate(c)) throw FTL_Error("Aggregate channels must be of list type"); + + ftl::data::verifyChannelType<T>(c); + + std::any &a = createAnyChange(c, type, data); + if (!isType<T>(c)) return a.emplace<T>(); + else return *std::any_cast<T>(&a); +} + +// Non-list version +template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> +T &ftl::data::Frame::createChange(ftl::codecs::Channel c, ftl::data::ChangeType type) { + if (isAggregate(c)) throw FTL_Error("Aggregate channels must be of list type"); + + ftl::data::verifyChannelType<T>(c); + + std::any &a = createAnyChange(c, type); + if (!isType<T>(c)) return a.emplace<T>(); + else return *std::any_cast<T>(&a); +} + +// List version +template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> +ftl::data::Aggregator<T> ftl::data::Frame::createChange(ftl::codecs::Channel c, ftl::data::ChangeType type) { + ftl::data::verifyChannelType<T>(c); + + std::any &a = createAnyChange(c, type); + if (!isType<T>(c)) a.emplace<T>(); + return ftl::data::Aggregator<T>{*std::any_cast<T>(&a), isAggregate(c)}; } template <typename T, typename ...ARGS> T &ftl::data::Frame::emplace(ftl::codecs::Channel c, ARGS... args) { touch(c); - return data_[c].emplace<T>(std::forward<ARGS...>(args...)); + return data_[c].data.emplace<T>(std::forward<ARGS...>(args...)); } -template <typename T> -void ftl::data::Frame::push(ftl::codecs::Channel c, const T &v) { +// Non-list version +template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> +T &ftl::data::Frame::set(ftl::codecs::Channel c) { + if (status_ != FrameStatus::STORED) throw FTL_Error("Cannot modify before store"); + auto i = data_.find(c); if (i != data_.end()) { - auto *p = std::any_cast<std::vector<T>>(&i->second); - p->push_back(v); + if (i->second.status != ftl::data::ChannelStatus::FLUSHED) { + i->second.encoded.clear(); + touch(c); + return *std::any_cast<T>(&i->second.data); + } else { + throw FTL_Error("Channel is flushed and read-only: " << static_cast<unsigned int>(c)); + } + } else if (parent_ && parent_->isType<T>(c)) { + touch(c); + return create<T>(c); } else { - throw FTL_Error("Push on missing channel (" << static_cast<unsigned int>(c) << ")"); + throw FTL_Error("Set 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) { +// List version +template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> +ftl::data::Aggregator<T> ftl::data::Frame::set(ftl::codecs::Channel c) { + if (status_ != FrameStatus::STORED) throw FTL_Error("Cannot modify before store"); + auto i = data_.find(c); if (i != data_.end()) { - i->second = v; + if (i->second.status != ftl::data::ChannelStatus::FLUSHED) { + i->second.encoded.clear(); + touch(c); + return ftl::data::Aggregator<T>{*std::any_cast<T>(&i->second.data), isAggregate(c)}; + } else { + throw FTL_Error("Channel is flushed and read-only: " << static_cast<unsigned int>(c)); + } } else if (parent_ && parent_->isType<T>(c)) { - create<T>(c, v); + touch(c); + return create<T>(c); } 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 index 36a8b0c64..7ed685d0c 100644 --- a/components/structures/src/new_frame.cpp +++ b/components/structures/src/new_frame.cpp @@ -1,15 +1,18 @@ #include <ftl/data/new_frame.hpp> +#include <ftl/data/framepool.hpp> using ftl::data::Frame; +using ftl::data::Session; using ftl::data::ChannelConfig; -using ftl::data::ChannelMode; +using ftl::data::StorageMode; +using ftl::data::FrameStatus; #define LOGURU_REPLACE_GLOG 1 #include <loguru.hpp> static std::unordered_map<ftl::codecs::Channel, ChannelConfig> reg_channels; -void Frame::registerChannel(ftl::codecs::Channel c, const ChannelConfig &config) { +void ftl::data::registerChannel(ftl::codecs::Channel c, const ChannelConfig &config) { auto i = reg_channels.find(c); if (i != reg_channels.end()) { throw FTL_Error("Channel " << static_cast<unsigned int>(c) << " already registered"); @@ -18,87 +21,277 @@ void Frame::registerChannel(ftl::codecs::Channel c, const ChannelConfig &config) reg_channels[c] = config; } -void Frame::clearRegistry() { +void ftl::data::clearRegistry() { reg_channels.clear(); } -bool Frame::isPersistent(ftl::codecs::Channel c) { +bool ftl::data::isPersistent(ftl::codecs::Channel c) { auto i = reg_channels.find(c); - return (i != reg_channels.end()) ? i->second.mode == ChannelMode::PERSISTENT : true; + return (i != reg_channels.end()) ? i->second.mode == StorageMode::PERSISTENT : false; } -size_t Frame::getChannelType(ftl::codecs::Channel c) { +bool ftl::data::isAggregate(ftl::codecs::Channel c) { + auto i = reg_channels.find(c); + return (i != reg_channels.end()) ? i->second.mode == StorageMode::AGGREGATE : false; +} + +size_t ftl::data::getChannelType(ftl::codecs::Channel c) { auto i = reg_channels.find(c); return (i != reg_channels.end()) ? i->second.type_id : 0; } -std::string Frame::getChannelName(ftl::codecs::Channel c) { +std::string ftl::data::getChannelName(ftl::codecs::Channel c) { auto i = reg_channels.find(c); return (i != reg_channels.end()) ? i->second.name : ""; } -ftl::codecs::Channel Frame::getChannelByName(const std::string &name) { +ftl::codecs::Channel ftl::data::getChannelByName(const std::string &name) { return ftl::codecs::Channel::Colour; } +Frame::~Frame() { + if (status_ == FrameStatus::CREATED) store(); + if (status_ == FrameStatus::STORED) flush(); + if (status_ != FrameStatus::RELEASED && pool_) pool_->release(*this); +}; + +bool ftl::data::Frame::has(ftl::codecs::Channel c) const { + const auto &i = data_.find(c); + if (i != data_.end() && i->second.status != ftl::data::ChannelStatus::INVALID) { + return true; + } else { + return (parent_ && parent_->has(c)); + } +} + +Frame::ChannelData &Frame::_getData(ftl::codecs::Channel c) { + if (status_ == FrameStatus::RELEASED) throw FTL_Error("Reading a released frame"); + const auto &i = data_.find(c); + if (i != data_.end()) { + return i->second; + } else if (parent_) { + return parent_->_getData(c); + } else throw FTL_Error("Missing channel (" << static_cast<unsigned int>(c) << ")"); +} + +const Frame::ChannelData &Frame::_getData(ftl::codecs::Channel c) const { + if (status_ == FrameStatus::RELEASED) throw FTL_Error("Reading a released frame"); + const auto &i = data_.find(c); + if (i != data_.end()) { + return i->second; + } else if (parent_) { + return parent_->_getData(c); + } else throw FTL_Error("Missing channel (" << static_cast<unsigned int>(c) << ")"); +} + +std::any &Frame::createAnyChange(ftl::codecs::Channel c, ftl::data::ChangeType t) { + if (status_ != FrameStatus::CREATED) throw FTL_Error("Cannot apply change after store " << static_cast<int>(status_)); + + auto &d = data_[c]; + if (d.status != ftl::data::ChannelStatus::FLUSHED) { + d.status = ftl::data::ChannelStatus::DISPATCHED; + d.encoded.clear(); + touch(c, t); + return d.data; + } else { + throw FTL_Error("Channel is flushed and read-only: " << static_cast<unsigned int>(c)); + } +} + +std::any &Frame::createAnyChange(ftl::codecs::Channel c, ftl::data::ChangeType t, std::vector<uint8_t> &data) { + if (status_ != FrameStatus::CREATED) throw FTL_Error("Cannot apply change after store " << static_cast<int>(status_)); + + auto &d = data_[c]; + if (d.status != ftl::data::ChannelStatus::FLUSHED) { + d.status = ftl::data::ChannelStatus::DISPATCHED; + d.encoded = std::move(data); + touch(c, t); + return d.data; + } else { + throw FTL_Error("Channel is flushed and read-only: " << static_cast<unsigned int>(c)); + } +} + +std::any &Frame::createAny(ftl::codecs::Channel c) { + if (status_ != FrameStatus::STORED) throw FTL_Error("Cannot create before store or after flush"); + + auto &d = data_[c]; + if (d.status != ftl::data::ChannelStatus::FLUSHED) { + d.status = ftl::data::ChannelStatus::VALID; + d.encoded.clear(); + touch(c); + return d.data; + } else { + throw FTL_Error("Channel is flushed and read-only: " << static_cast<unsigned int>(c)); + } +} + +std::any &Frame::getAnyMutable(ftl::codecs::Channel c) { + auto &d = _getData(c); + return d.data; +} + +const std::vector<uint8_t> &ftl::data::Frame::getEncoded(ftl::codecs::Channel c) const { + const auto &d = _getData(c); + if (d.status != ftl::data::ChannelStatus::INVALID) { + return d.encoded; + } else throw FTL_Error("Missing channel (" << static_cast<unsigned int>(c) << ")"); +} bool Frame::flush() { + if (status_ == FrameStatus::CREATED) throw FTL_Error("Frame cannot be flushed before store"); + if (status_ == FrameStatus::FLUSHED) throw FTL_Error("Frame cannot be flushed twice"); + status_ = FrameStatus::FLUSHED; + 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(); - } - } - } - } + parent_->flush(*this); + } + for (auto c : changed_) { + _getData(c.first).status = ChannelStatus::FLUSHED; } - changed_.clear(); return true; } +bool Frame::flush(ftl::codecs::Channel c) { + if (status_ == FrameStatus::CREATED) throw FTL_Error("Frame cannot be flushed before store"); + if (status_ == FrameStatus::FLUSHED) throw FTL_Error("Frame cannot be flushed twice"); + //status_ = FrameStatus::FLUSHED; + + if (parent_ && changed(c)) { + parent_->flush(*this, c); + _getData(c).status = ChannelStatus::FLUSHED; + } + return true; +} + +void Frame::store() { + if (status_ != FrameStatus::CREATED) throw FTL_Error("Frame cannot be stored twice"); + status_ = FrameStatus::STORED; + + if (!parent_) return; + + for (auto c : changed_) { + if (ftl::data::isPersistent(c.first) && hasOwn(c.first)) { + auto &d = data_[c.first]; + auto &pd = parent_->data_[c.first]; + pd.data = d.data; + //pd.encoded = std::move(d.encoded); + pd.status = ChannelStatus::VALID; + //data_.erase(c.first); + } + + parent_->change_.trigger(*this, c.first); + uint64_t sig = (uint64_t(id) << 32) + static_cast<unsigned int>(c.first); + const auto &i = parent_->change_channel_.find(sig); + if (i != parent_->change_channel_.end()) i->second.trigger(*this, c.first); + } +} + void Frame::merge(Frame &f) { - for (auto x : f) { - data_[x.first] = std::move(x.second); + for (auto &x : f) { + auto &d = data_[x.first]; + d.data = std::move(x.second.data); + d.encoded = std::move(x.second.encoded); + f.data_[x.first].status = ChannelStatus::INVALID; + d.status = ChannelStatus::VALID; touch(x.first); } } -void Frame::swapTo(Frame &f) { - for (auto x : *this) { - f.data_[x.first].swap(x.second); - f.changed_.emplace(x.first); - changed_.emplace(x.first); - } +void Frame::moveTo(Frame &f) { + if (status_ == FrameStatus::RELEASED) throw FTL_Error("Moving released frame"); + f.status_ = status_; + f.parent_ = parent_; + f.pool_ = pool_; + f.data_ = std::move(data_); + f.changed_ = std::move(changed_); + status_ = FrameStatus::RELEASED; } void Frame::swapChanged(Frame &f) { for (auto x : changed_) { - f.data_[x].swap(data_[x]); - f.changed_.emplace(x); + f.data_[x.first].data.swap(data_[x.first].data); + f.changed_[x.first] = ftl::data::ChangeType::LOCAL; } } void Frame::swapChannel(ftl::codecs::Channel c, Frame &f) { - if (has(c)) { - f.data_[c].swap(data_[c]); - f.changed_.emplace(c); - changed_.emplace(c); + if (f.hasOwn(c)) { + auto &d = data_[c]; + auto &fd = f.data_[c]; + fd.data.swap(d.data); + d.status = ftl::data::ChannelStatus::VALID; + changed_[c] = f.changed_[c]; + f.changed_[c] = ftl::data::ChangeType::LOCAL; } } -void Frame::clear() { +void Frame::reset() { + for (auto &d : data_) { + d.second.status = ChannelStatus::INVALID; + } + changed_.clear(); + status_ = FrameStatus::CREATED; +} + +void Frame::hardReset() { + status_ = FrameStatus::CREATED; changed_.clear(); data_.clear(); } + +// ==== Session ================================================================ + +ftl::Handle Session::onChange(uint32_t id, ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + uint64_t sig = (uint64_t(id) << 32) + static_cast<unsigned int>(c); + return change_channel_[sig].on(cb); +} + +ftl::Handle Session::onChange(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + return change_.on(cb); +} + +ftl::Handle Session::onFlush(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb) { + return flush_.on(cb); +} + +void Session::notifyChanges(Frame &f) { + +} + +void Session::flush(Frame &f) { + // TODO: Lock + for (auto c : f.changed()) { + if (c.second == ftl::data::ChangeType::LOCAL) { + auto &d = f._getData(c.first); + if (d.status == ftl::data::ChannelStatus::VALID) { + d.status = ftl::data::ChannelStatus::FLUSHED; + flush_.trigger(f, c.first); + } + } else if (c.second == ftl::data::ChangeType::FOREIGN) { + auto &d = f._getData(c.first); + if (d.status == ftl::data::ChannelStatus::DISPATCHED) { + d.status = ftl::data::ChannelStatus::FLUSHED; + flush_.trigger(f, c.first); + } + } + } +} + +void Session::flush(Frame &f, ftl::codecs::Channel c) { + // TODO: Lock + auto cc = f.changed_[c]; + if (cc == ftl::data::ChangeType::LOCAL) { + auto &d = f._getData(c); + if (d.status == ftl::data::ChannelStatus::VALID) { + d.status = ftl::data::ChannelStatus::FLUSHED; + flush_.trigger(f, c); + } + } else if (cc == ftl::data::ChangeType::FOREIGN) { + auto &d = f._getData(c); + if (d.status == ftl::data::ChannelStatus::DISPATCHED) { + d.status = ftl::data::ChannelStatus::FLUSHED; + flush_.trigger(f, c); + } + } +} diff --git a/components/structures/src/pool.cpp b/components/structures/src/pool.cpp new file mode 100644 index 000000000..dd22c3b46 --- /dev/null +++ b/components/structures/src/pool.cpp @@ -0,0 +1,75 @@ +#include <ftl/data/framepool.hpp> + +using ftl::data::Pool; +using ftl::data::Frame; +using ftl::data::Session; + +Pool::Pool(size_t min_n, size_t max_n) : min_n_(min_n), max_n_(max_n) { + ideal_n_ = min_n + (max_n-min_n)/2; +} + +Pool::~Pool() { + for (auto &p : pool_) { + for (auto *f : p.second.pool) { + f->status_ = FrameStatus::RELEASED; + delete f; + } + } +} + +Frame Pool::allocate(uint32_t id, int64_t timestamp) { + auto &pool = _getPool(id); + + if (timestamp < pool.last_timestamp) { + throw FTL_Error("New frame timestamp is older that previous: " << timestamp); + } + + // Add items as required + if (pool.pool.size() < min_n_) { + while (pool.pool.size() < ideal_n_) { + pool.pool.push_back(new Frame(this, &pool.session, id, 0)); + } + } + + Frame *f = pool.pool.front(); + Frame ff = std::move(*f); + delete f; + ff.restart(timestamp); + pool.pool.pop_front(); + pool.last_timestamp = timestamp; + return ff; +} + +void Pool::release(Frame &f) { + if (f.status() == FrameStatus::RELEASED) return; + f.reset(); + auto &pool = _getPool(f.id); + + if (pool.pool.size() < max_n_) { + Frame *pf = new Frame(this, &pool.session, f.id, 0); + f.moveTo(*pf); + pool.pool.push_back(pf); + } +} + +ftl::data::Session &Pool::session(uint32_t id) { + auto &pool = _getPool(id); + return pool.session; +} + +size_t Pool::size(uint32_t id) { + auto &pool = _getPool(id); + return pool.pool.size(); +} + +size_t Pool::size() { + size_t s = 0; + for (auto &p : pool_) { + s += p.second.pool.size(); + } + return s; +} + +ftl::data::Pool::PoolData &Pool::_getPool(uint32_t id) { + return pool_[id]; +} diff --git a/components/structures/test/CMakeLists.txt b/components/structures/test/CMakeLists.txt index 155634bc4..653cfd3c4 100644 --- a/components/structures/test/CMakeLists.txt +++ b/components/structures/test/CMakeLists.txt @@ -3,7 +3,23 @@ add_executable(nframe_unit $<TARGET_OBJECTS:CatchTest> ./frame_unit.cpp ../src/new_frame.cpp + ./frame_example_1.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 + ftlcommon ftlcodecs) + +add_test(NFrameUnitTest nframe_unit) + +### Pool Unit ################################################################## +add_executable(pool_unit + $<TARGET_OBJECTS:CatchTest> + ./pool_unit.cpp + ../src/new_frame.cpp + ../src/pool.cpp +) +target_include_directories(pool_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") +target_link_libraries(pool_unit + ftlcommon ftlcodecs) + +add_test(MemPoolUnitTest pool_unit) \ No newline at end of file diff --git a/components/structures/test/frame_example_1.cpp b/components/structures/test/frame_example_1.cpp new file mode 100644 index 000000000..8be10efaa --- /dev/null +++ b/components/structures/test/frame_example_1.cpp @@ -0,0 +1,135 @@ +#include "catch.hpp" + +#include <ftl/data/new_frame.hpp> + +using ftl::data::Session; +using ftl::data::Frame; +using ftl::codecs::Channel; +using ftl::data::ChangeType; +using ftl::data::StorageMode; + +namespace ftl { +namespace streams { + +/* Mock Feed class */ +class Feed { + public: + Feed() : buffer0_(nullptr, &session_,0,0), buffer1_(nullptr, &session_,0,0) { + flush_handle_ = session_.onFlush([this](Frame &f, Channel c) { + // Loop changes back to buffer. + // Normally transmitted somewhere first. + //buffer_.swapChannel(c, f); + ChangeType cc = f.getChangeType(c); + buffer0_.createAnyChange(c, (cc == ChangeType::LOCAL) ? ChangeType::FOREIGN : ChangeType::COMPLETED) = f.getAnyMutable(c); + return true; + }); + } + + inline Frame &buffer() { return buffer0_; } + inline Session &session() { return session_; } + + inline void fakeDispatch() { + buffer0_.moveTo(buffer1_); + buffer0_ = Frame(nullptr, &session_,0,0); + + // Save any persistent changes + buffer1_.store(); + // Transmit any forwarding changes and prevent further changes + buffer1_.flush(); // TODO: use special dispatched function + + // Call the onFrame handler. + // Would be in another thread in real version of this class. + frame_handler_.trigger(buffer1_); + } + + inline ftl::Handle onFrame(const std::function<bool(Frame&)> &cb) { + return frame_handler_.on(cb); + } + + Frame createFrame(int id) { + // TODO: Give it the id and a timestamp + Frame f(nullptr, &session_,0,0); + f.store(); + return f; + } + + private: + ftl::Handler<Frame&> frame_handler_; + Session session_; + Frame buffer0_; + Frame buffer1_; + ftl::Handle flush_handle_; +}; + +} +} + +using ftl::streams::Feed; + +/* Test class */ +struct VideoFrame { + int gpudata; + int matdata; +}; + +TEST_CASE("ftl::data::Frame full non-owner example", "[example]") { + // Register channels somewhere at startup + ftl::data::make_channel<VideoFrame>(Channel::Colour, "colour", StorageMode::TRANSIENT); + ftl::data::make_channel<VideoFrame>(Channel::Depth, "depth", StorageMode::TRANSIENT); + ftl::data::make_channel<std::list<std::string>>(Channel::Messages, "messages", StorageMode::AGGREGATE); + ftl::data::make_channel<float>(Channel::Pose, "pose", StorageMode::PERSISTENT); + + Feed feed; + + int i=0; + int changed = 0; + ftl::Handle myhandle; + + auto h = feed.onFrame([&i,&feed,&myhandle,&changed](Frame &f) { + // First frame received + if (i++ == 0 ) { + // User of Frame makes changes or reads values from state + REQUIRE( f.get<float>(Channel::Pose) == 6.0f ); + REQUIRE( f.get<VideoFrame>(Channel::Depth).gpudata == 1 ); + + // Create a new frame for same source for some return state + Frame nf = feed.createFrame(f.id); + nf.create<std::list<std::string>>(Channel::Messages) = {"First Message"}; + nf.create<std::list<std::string>>(Channel::Messages) = {"Second Message"}; + nf.create<int>(Channel::Control) = 3456; + //nf.set<float>(Channel::Pose) = 7.0f; + + // Listen for this `Control` change to be confirmed + myhandle = nf.onChange(Channel::Control, [&changed](Frame &f, Channel c) { + changed++; + return false; // Call once only + }); + + // Either by destruction or manually, final action is flush to send + nf.flush(); + // Second frame received + } else { + REQUIRE( changed == 1 ); // Change notified before `onFrame` + REQUIRE( f.get<float>(Channel::Pose) == 6.0f ); + REQUIRE( f.get<int>(Channel::Control) == 3456 ); + REQUIRE( (*f.get<std::list<std::string>>(Channel::Messages).begin()) == "First Message" ); + } + return true; + }); + + // Generate some incoming changes from network + // Usually this is done inside the Feed class... + feed.buffer().createChange<VideoFrame>(Channel::Colour, ChangeType::FOREIGN).gpudata = 1; + feed.buffer().createChange<VideoFrame>(Channel::Depth, ChangeType::COMPLETED).gpudata = 1; + feed.buffer().createChange<float>(Channel::Pose, ChangeType::FOREIGN) = 6.0f; + + // Fake a frame being received on network or from file + feed.fakeDispatch(); + // And dispatch the response frame also + feed.fakeDispatch(); + + REQUIRE( i == 2 ); + + // For testing only... + ftl::data::clearRegistry(); +} diff --git a/components/structures/test/frame_unit.cpp b/components/structures/test/frame_unit.cpp index 7cf289868..265fa6e9d 100644 --- a/components/structures/test/frame_unit.cpp +++ b/components/structures/test/frame_unit.cpp @@ -1,3 +1,8 @@ +/* + * These tests directly relate to the specification found at: + * https://gitlab.utu.fi/nicolas.pope/ftl/-/wikis/Design/Frames + */ + #include "catch.hpp" #include <ftl/data/new_frame.hpp> @@ -5,19 +10,122 @@ using ftl::data::Session; using ftl::data::Frame; using ftl::codecs::Channel; +using ftl::data::ChangeType; +using ftl::data::StorageMode; + +namespace ftl { +namespace data { + +class Pool { + public: + static Frame make(Session *s, int id, uint64_t ts) { return Frame(nullptr, s, id, ts); } + + void release(Frame &f); +}; + +} + +namespace streams { + +// Only Pool can create frames so make a mock Feed. +class Feed { + public: + static Frame make(Session *s, int id, uint64_t ts) { return ftl::data::Pool::make(s, id, ts); } +}; + +} +} + +using ftl::streams::Feed; + +void ftl::data::Pool::release(Frame &f) { + +} + +/* #1.1.1 */ +static_assert(sizeof(ftl::codecs::Channel) >= 4, "Channel must be at least 32bit"); + +/* #1.1.2 */ +static_assert(std::is_integral<decltype(ftl::data::Frame::id)>::value, "Integral ID requried in Frame"); +static_assert(std::is_member_function_pointer<decltype(&ftl::data::Frame::timestamp)>::value, "Timestamp is required"); + +/* #1.1.3 */ +static_assert(std::is_member_function_pointer<decltype(&ftl::data::Frame::mutex)>::value, "Frame::mutex is not a member function."); + +/* #1.1.4 */ +TEST_CASE("ftl::data::Frame encoded data", "[1.1.4]") { + SECTION("provide encoded data") { + Frame f = Feed::make(nullptr, 0, 0); + std::vector<uint8_t> data{44,22,33}; + + f.createChange<int>(Channel::Pose, ftl::data::ChangeType::FOREIGN, data) = 55; + const auto &x = f.get<int>(Channel::Pose); + REQUIRE( x == 55 ); + + // Data has been moved. + REQUIRE(data.size() == 0); + } + + SECTION("get encoded data") { + Frame f = Feed::make(nullptr, 0, 0); + std::vector<uint8_t> data{44,22,33}; + + f.createChange<int>(Channel::Pose, ftl::data::ChangeType::FOREIGN, data); + + auto &data2 = f.getEncoded(Channel::Pose); + REQUIRE( data2.size() == 3 ); + REQUIRE( data2[0] == 44 ); + } +} + +/* #1.1.5 */ +TEST_CASE("ftl::data::Frame clear encoded on change", "[1.1.5]") { + SECTION("change by set") { + Frame f = Feed::make(nullptr, 0, 0); + std::vector<uint8_t> data{44,22,33}; + f.createChange<int>(Channel::Pose, ftl::data::ChangeType::FOREIGN, data); + f.store(); + + auto &data2 = f.getEncoded(Channel::Pose); + REQUIRE( data2.size() == 3 ); + + f.set<int>(Channel::Pose) = 66; + REQUIRE(f.getEncoded(Channel::Pose).size() == 0); + } + + SECTION("change by create") { + Frame f = Feed::make(nullptr, 0, 0); + std::vector<uint8_t> data{44,22,33}; + + f.createChange<int>(Channel::Pose, ftl::data::ChangeType::FOREIGN, data); + f.store(); + + auto &data2 = f.getEncoded(Channel::Pose); + REQUIRE( data2.size() == 3 ); + + f.create<int>(Channel::Pose) = 66; + REQUIRE(f.getEncoded(Channel::Pose).size() == 0); + } +} + +/* #1.2.1 */ TEST_CASE("ftl::data::Frame create get", "[Frame]") { SECTION("write and read integers") { - Frame f; - f.create<int>(Channel::Pose, 55); + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Pose) = 55; const auto &x = f.get<int>(Channel::Pose); REQUIRE( x == 55 ); } SECTION("write and read floats") { - Frame f; - f.create<float>(Channel::Pose, 44.0f); + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<float>(Channel::Pose) = 44.0f; const auto &x = f.get<float>(Channel::Pose); REQUIRE( x == 44.0f ); @@ -28,38 +136,21 @@ TEST_CASE("ftl::data::Frame create get", "[Frame]") { int a=44; float b=33.0f; }; - Frame f; - f.create<Test>(Channel::Pose, {}); + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<Test>(Channel::Pose) = {}; const auto &x = f.get<Test>(Channel::Pose); 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, {}); - - bool err = false; - - try { - int x = f.get<int>(Channel::Pose); - REQUIRE(x); - } catch (...) { - err = true; - } - REQUIRE(err); - } -} - -TEST_CASE("ftl::data::Frame isType", "[Frame]") { SECTION("is int type") { - Frame f; - f.create<int>(Channel::Pose, 55); + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Pose) = 55; REQUIRE( f.isType<int>(Channel::Pose) ); REQUIRE( !f.isType<float>(Channel::Pose) ); @@ -70,44 +161,93 @@ TEST_CASE("ftl::data::Frame isType", "[Frame]") { int a; int b; }; - Frame f; - f.create<Test>(Channel::Pose, {3,4}); + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<Test>(Channel::Pose) = {3,4}; REQUIRE( f.isType<Test>(Channel::Pose) ); REQUIRE( !f.isType<float>(Channel::Pose) ); } SECTION("missing") { - Frame f; + Frame f = Feed::make(nullptr, 0, 0); REQUIRE( !f.isType<float>(Channel::Pose) ); } } -TEST_CASE("ftl::data::Frame changed", "[Frame]") { - SECTION("change on create") { - Frame f; +/* #1.2.2 */ +TEST_CASE("ftl::data::registerChannel", "[Frame]") { + SECTION("register typed channel and valid create") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); - REQUIRE( !f.changed(Channel::Pose) ); - f.create<int>(Channel::Pose, 55); - REQUIRE( f.changed(Channel::Pose) ); + ftl::data::make_channel<float>(Channel::Colour, "colour", ftl::data::StorageMode::PERSISTENT); + f.create<float>(Channel::Colour) = 5.0f; + REQUIRE( f.get<float>(Channel::Colour) == 5.0f ); + + ftl::data::clearRegistry(); } - SECTION("no change on untouch") { - Frame f; + SECTION("register typed channel and invalid create") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); - f.create<int>(Channel::Pose, 55); - REQUIRE( f.changed(Channel::Pose) ); - f.untouch(Channel::Pose); - REQUIRE( !f.changed(Channel::Pose) ); + ftl::data::make_channel<float>(Channel::Colour, "colour", ftl::data::StorageMode::PERSISTENT); + + bool err = false; + try { + f.create<int>(Channel::Colour) = 5; + } catch(const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + + ftl::data::clearRegistry(); + } + + SECTION("register void for any type") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + ftl::data::make_channel<void>(Channel::Colour, "colour", ftl::data::StorageMode::PERSISTENT); + + f.create<int>(Channel::Colour) = 5; + REQUIRE( f.get<int>(Channel::Colour) == 5 ); + + ftl::data::clearRegistry(); } } -TEST_CASE("ftl::data::Frame create", "[Frame]") { +/* #1.2.3 */ +TEST_CASE("ftl::data::Frame type failure") { + SECTION("write and read fail") { + struct Test { + int a=44; + float b=33.0f; + }; + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + f.create<Test>(Channel::Pose) = {}; + + bool err = false; + + try { + f.get<int>(Channel::Pose); + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE(err); + } + SECTION("same value on create") { - Frame f; + Frame f = Feed::make(nullptr, 0, 0); + f.store(); - f.create<int>(Channel::Pose, 55); + f.create<int>(Channel::Pose) = 55; const auto &x = f.get<int>(Channel::Pose); REQUIRE( x == 55 ); @@ -116,10 +256,11 @@ TEST_CASE("ftl::data::Frame create", "[Frame]") { REQUIRE( y == 55 ); } - SECTION("change of type") { - Frame f; + SECTION("change of type by recreate") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); - f.create<int>(Channel::Pose, 55); + f.create<int>(Channel::Pose) = 55; auto x = f.getPtr<int>(Channel::Pose); REQUIRE( x ); REQUIRE( *x == 55 ); @@ -131,12 +272,58 @@ TEST_CASE("ftl::data::Frame create", "[Frame]") { } } -TEST_CASE("ftl::data::Frame use of parent", "[Frame]") { - SECTION("get from parent") { +/* #1.2.4 UNTESTED */ + +/* #1.2.5 */ +TEST_CASE("ftl::data::Frame persistent data", "[1.2.5]") { + ftl::data::make_channel<int>(Channel::Density, "density", ftl::data::StorageMode::PERSISTENT); + + SECTION("persistent through createChange") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.createChange<int>(Channel::Density, ChangeType::FOREIGN) = 44; + f.store(); + REQUIRE( p.get<int>(Channel::Density) == 44 ); + } + + // These are not valid as per #3.2.5 + /*SECTION("persistent via create") { Session p; Frame f(&p); - p.create<int>(Channel::Pose, 55); + f.create<int>(Channel::Density, 44); + f.store(); + REQUIRE( p.get<int>(Channel::Density) == 44 ); + } + + SECTION("persistent via set") { + Session p; + Frame f(&p); + + f.create<int>(Channel::Density, 44); + f.set<int>(Channel::Density, 45); + f.store(); + REQUIRE( p.get<int>(Channel::Density) == 45 ); + }*/ + + SECTION("available in other frame") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.createChange<int>(Channel::Density, ChangeType::FOREIGN) = 44; + f.store(); + + Frame f2 = Feed::make(&p, 0, 0); + REQUIRE( f2.get<int>(Channel::Density) == 44 ); + } + + SECTION("get from parent") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + p.create<int>(Channel::Pose) = 55; auto x = f.getPtr<int>(Channel::Pose); REQUIRE( x ); @@ -148,23 +335,25 @@ TEST_CASE("ftl::data::Frame use of parent", "[Frame]") { SECTION("has from parent") { Session p; - Frame f(&p); + Frame f = Feed::make(&p, 0, 0); + f.store(); - p.create<int>(Channel::Pose, 55); + p.create<int>(Channel::Pose) = 55; REQUIRE( f.has(Channel::Pose) ); } SECTION("no change in parent") { Session p; - Frame f(&p); + Frame f = Feed::make(&p, 0, 0); + f.store(); - p.create<int>(Channel::Pose, 55); + 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); + f.set<int>(Channel::Pose) = 66; REQUIRE( f.changed(Channel::Pose) ); REQUIRE( !p.changed(Channel::Pose) ); @@ -177,168 +366,1042 @@ TEST_CASE("ftl::data::Frame use of parent", "[Frame]") { REQUIRE( y ); REQUIRE( *y == 55 ); } + + ftl::data::clearRegistry(); } -TEST_CASE("ftl::data::Frame flush", "[Frame]") { - SECTION("event on flush") { - Frame f; +/* #1.2.6 */ +TEST_CASE("ftl::data::Frame transient data", "[1.2.6]") { + ftl::data::make_channel<int>(Channel::Density, "density", ftl::data::StorageMode::TRANSIENT); - int event = 0; - f.on(Channel::Pose, [&event](Frame &frame, Channel c) { - event++; - return true; - }); + SECTION("not persistent after store") { + Session p; + Frame f = Feed::make(&p, 0, 0); - f.create<int>(Channel::Pose, 55); - REQUIRE( event == 0 ); + f.createChange<int>(Channel::Density, ChangeType::FOREIGN) = 44; + f.store(); + + REQUIRE( !p.has(Channel::Density) ); + } - f.flush(); - REQUIRE( event == 1 ); + ftl::data::clearRegistry(); +} + +/* #1.2.7 */ +TEST_CASE("ftl::data::Frame aggregate data", "[1.2.7]") { + ftl::data::make_channel<void>(Channel::Density, "density", ftl::data::StorageMode::AGGREGATE); + + SECTION("not persistent after store") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {44}; + f.store(); + + REQUIRE( !p.has(Channel::Density) ); } - SECTION("parent event on flush") { + // TODO: Check elsewhere that the changes are since last frame, not + // applicable as part of this unit test. + + SECTION("aggregate channels actually aggregate with createChange") { Session p; - Frame f(&p); + Frame f = Feed::make(&p, 0, 0); + + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {34}; + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {55}; + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {12,89}; + f.store(); + + auto list = f.get<std::list<int>>(Channel::Density).begin(); + REQUIRE( *(list++) == 34 ); + REQUIRE( *(list++) == 55 ); + REQUIRE( *(list++) == 12 ); + REQUIRE( *(list++) == 89 ); + } - int event = 0; - p.on(Channel::Pose, [&event](Frame &frame, Channel c) { - event++; - return true; - }); + SECTION("non aggregate channels do not aggregate with createChange") { + Session p; + Frame f = Feed::make(&p, 0, 0); - f.create<int>(Channel::Pose, 55); - REQUIRE( event == 0 ); + f.createChange<std::list<int>>(Channel::Colour, ChangeType::FOREIGN) = {34}; + f.createChange<std::list<int>>(Channel::Colour, ChangeType::FOREIGN) = {55}; + f.createChange<std::list<int>>(Channel::Colour, ChangeType::FOREIGN) = {12,89}; + f.store(); - f.flush(); - REQUIRE( event == 1 ); + auto list = f.get<std::list<int>>(Channel::Colour).begin(); + REQUIRE( *(list++) == 12 ); + REQUIRE( *(list++) == 89 ); } - SECTION("parent change on flush") { + SECTION("aggregate channels allow move aggregate with createChange") { Session p; - Frame f(&p); + Frame f = Feed::make(&p, 0, 0); - p.create<int>(Channel::Pose, 55); - p.flush(); + std::list<int> data1 = {34}; + std::list<int> data2 = {55}; - f.set<int>(Channel::Pose, 66); - auto x = p.getPtr<int>(Channel::Pose); - REQUIRE( x ); - REQUIRE( *x == 55 ); - - f.flush(); - x = p.getPtr<int>(Channel::Pose); - REQUIRE( x ); - REQUIRE( *x == 66 ); + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = std::move(data1); + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = std::move(data2); + f.store(); + + auto list = f.get<std::list<int>>(Channel::Density).begin(); + REQUIRE( *(list++) == 34 ); + REQUIRE( *(list++) == 55 ); + REQUIRE( data1.size() == 0 ); + REQUIRE( data2.size() == 0 ); + } + + SECTION("aggregate channels actually aggregate with create") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + f.create<std::list<int>>(Channel::Density) = {34}; + f.create<std::list<int>>(Channel::Density) = {55}; + f.create<std::list<int>>(Channel::Density) = {12,89}; + + auto list = f.get<std::list<int>>(Channel::Density).begin(); + REQUIRE( *(list++) == 34 ); + REQUIRE( *(list++) == 55 ); + REQUIRE( *(list++) == 12 ); + REQUIRE( *(list++) == 89 ); } - SECTION("untouched on flush") { - Frame f; + SECTION("non aggregate channels do not aggregate with create") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); - f.create<int>(Channel::Pose, 55); - REQUIRE( f.changed(Channel::Pose) ); + f.create<std::list<int>>(Channel::Colour) = {34}; + f.create<std::list<int>>(Channel::Colour) = {55}; + f.create<std::list<int>>(Channel::Colour) = {12,89}; - f.flush(); - REQUIRE( !f.changed(Channel::Pose) ); + auto list = f.get<std::list<int>>(Channel::Colour).begin(); + REQUIRE( *(list++) == 12 ); + REQUIRE( *(list++) == 89 ); } -} -TEST_CASE("ftl::data::Frame register", "[Frame]") { - SECTION("register typed channel and valid create") { - Frame f; + SECTION("aggregate channels actually aggregate with set") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + f.create<std::list<int>>(Channel::Density) = {34}; + f.set<std::list<int>>(Channel::Density) = {55}; + f.set<std::list<int>>(Channel::Density) = {12,89}; + + auto list = f.get<std::list<int>>(Channel::Density).begin(); + REQUIRE( *(list++) == 34 ); + REQUIRE( *(list++) == 55 ); + REQUIRE( *(list++) == 12 ); + REQUIRE( *(list++) == 89 ); + } + + SECTION("non aggregate channels do not aggregate with set") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); - Frame::registerChannel(Channel::Colour, ftl::data::make_channel<float>("colour", ftl::data::ChannelMode::PERSISTENT)); - REQUIRE( f.create<float>(Channel::Colour, 5.0f) == 5.0f ); + f.create<std::list<int>>(Channel::Colour) = {34}; + f.set<std::list<int>>(Channel::Colour) = {55}; + f.set<std::list<int>>(Channel::Colour) = {12,89}; - Frame::clearRegistry(); + auto list = f.get<std::list<int>>(Channel::Colour).begin(); + REQUIRE( *(list++) == 12 ); + REQUIRE( *(list++) == 89 ); } - SECTION("register typed channel and invalid create") { - Frame f; + ftl::data::clearRegistry(); +} + +/* #1.2.8 Not applicable as a unit test of Frame. */ + +/* #1.2.9 */ +TEST_CASE("ftl::data::Frame aggregate lists", "[1.2.9]") { + ftl::data::make_channel<void>(Channel::Density, "density", ftl::data::StorageMode::AGGREGATE); + + SECTION("only allow stl list container") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); - Frame::registerChannel(Channel::Colour, ftl::data::make_channel<float>("colour", ftl::data::ChannelMode::PERSISTENT)); + f.create<std::list<int>>(Channel::Density) = {44}; bool err = false; + try { - f.create<int>(Channel::Colour, 5); - } catch(const std::exception &e) { + f.create<int>(Channel::Density); + } catch (const ftl::exception &e) { + e.ignore(); err = true; } - REQUIRE( err ); - Frame::clearRegistry(); + REQUIRE( err ); } + + ftl::data::clearRegistry(); } -// ==== Complex type overload test ============================================= +/* #1.3 Not applicable as a unit test of Frame. */ -struct TestA { - int a=55; -}; +/* #2.1.1 */ +static_assert(!std::is_default_constructible<Frame>::value, "Must not have default construction"); +// TODO: Check for privacy of actual constructor? Proposed for future feature of C++ but not yet. -struct TestB { - int b=99; -}; +/* #2.1.2 */ +static_assert(!std::is_copy_constructible<Frame>::value, "Must not have a copy constructor"); +static_assert(!std::is_copy_assignable<Frame>::value, "Must not allow copy assignment"); -struct TestC { - TestA a; - TestB b; -}; +/* #2.1.3 */ +static_assert(std::is_move_constructible<Frame>::value, "Must have a move constructor"); +static_assert(std::is_move_assignable<Frame>::value, "Must allow move assignment"); -template <> -TestA &ftl::data::Frame::create<TestA>(ftl::codecs::Channel c) { - return create<TestC>(c).a; -} +/* #2.1.4 Not applicable as a unit test of Frame. */ -template <> -TestA &ftl::data::Frame::create<TestA>(ftl::codecs::Channel c, const TestA &a) { - TestC cc; - cc.a = a; - return create<TestC>(c, cc).a; -} +/* #2.1.5 Not applicable as a unit test of Frame. */ -template <> -TestB &ftl::data::Frame::create<TestB>(ftl::codecs::Channel c, const TestB &b) { - TestC cc; - cc.b = b; - return create<TestC>(c, cc).b; -} +/* #2.1.6 Not applicable as a unit test of Frame. */ -template <> -const TestA *ftl::data::Frame::getPtr<TestA>(ftl::codecs::Channel c) const noexcept { - auto *ptr = getPtr<TestC>(c); - return (ptr) ? &ptr->a : nullptr; +/* #2.1.7 Not applicable as a unit test of Frame. */ + +/* #2.1.8 */ +TEST_CASE("ftl::data::Frame merging", "[2.1.8]") { + SECTION("merge replaces data in destination") { + Frame f1 = Feed::make(nullptr, 0, 0); + Frame f2 = Feed::make(nullptr, 0, 0); + f1.store(); + f2.store(); + + f1.create<int>(Channel::Colour) = 43; + f1.create<int>(Channel::Colour2) = 77; + + f2.create<int>(Channel::Colour2) = 88; + + f2.merge(f1); + + REQUIRE( f2.get<int>(Channel::Colour2) == 77 ); + } + + SECTION("new items are created") { + Frame f1 = Feed::make(nullptr, 0, 0); + Frame f2 = Feed::make(nullptr, 0, 0); + f1.store(); + f2.store(); + + f1.create<int>(Channel::Colour) = 43; + f1.create<int>(Channel::Colour2) = 77; + + f2.create<int>(Channel::Colour2) = 88; + + f2.merge(f1); + + REQUIRE( f2.get<int>(Channel::Colour) == 43 ); + } + + SECTION("old items remain") { + Frame f1 = Feed::make(nullptr, 0, 0); + Frame f2 = Feed::make(nullptr, 0, 0); + f1.store(); + f2.store(); + + f1.create<int>(Channel::Colour2) = 77; + + f2.create<int>(Channel::Colour) = 43; + f2.create<int>(Channel::Colour2) = 88; + + f2.merge(f1); + + REQUIRE( f2.get<int>(Channel::Colour) == 43 ); + } + + SECTION("flushed status is removed") { + Frame f1 = Feed::make(nullptr, 0, 0); + Frame f2 = Feed::make(nullptr, 0, 0); + f1.store(); + f2.store(); + + f1.create<int>(Channel::Colour) = 43; + f1.flush(); + + REQUIRE( f1.flushed(Channel::Colour) ); + + f2.merge(f1); + + REQUIRE( !f2.flushed(Channel::Colour) ); + REQUIRE( f2.has(Channel::Colour) ); + } } -template <> -const TestB *ftl::data::Frame::getPtr<TestB>(ftl::codecs::Channel c) const noexcept { - auto *ptr = getPtr<TestC>(c); - return (ptr) ? &ptr->b : nullptr; +/* #2.1.9 */ +TEST_CASE("ftl::data::Frame merge is change", "[2.1.9]") { + SECTION("merges are marked as changes") { + Frame f1 = Feed::make(nullptr, 0, 0); + Frame f2 = Feed::make(nullptr, 0, 0); + f1.store(); + f2.store(); + + f1.create<int>(Channel::Colour) = 43; + f2.create<int>(Channel::Colour2) = 88; + f2.untouch(Channel::Colour2); + f2.merge(f1); + + REQUIRE( f2.getChangeType(Channel::Colour) == ChangeType::LOCAL ); + REQUIRE( !f2.changed(Channel::Colour2) ); + } } -TEST_CASE("ftl::data::Frame Complex Overload", "[Frame]") { - SECTION("Create and get first type with default") { - Frame f; - f.create<TestA>(Channel::Pose); - - auto *x = f.getPtr<TestA>(Channel::Pose); - REQUIRE( x ); - REQUIRE( x->a == 55 ); +/* #2.1.10 Unimplemented, merge is move only. This tests for the move instead */ +TEST_CASE("ftl::data::Frame merge moves encoded", "[2.1.10]") { + SECTION("encoded data moved") { + Frame f1 = Feed::make(nullptr, 0, 0); + Frame f2 = Feed::make(nullptr, 0, 0); - auto *y = f.getPtr<TestB>(Channel::Pose); - REQUIRE( y ); - REQUIRE( y->b == 99 ); + std::vector<uint8_t> data{88,99,100}; + f1.createChange<int>(Channel::Colour, ChangeType::FOREIGN, data); + f2.merge(f1); + + REQUIRE( f2.getEncoded(Channel::Colour).size() == 3 ); + REQUIRE( !f1.has(Channel::Colour) ); } +} - SECTION("Create and get first type with value") { - Frame f; - f.create<TestA>(Channel::Pose, {77}); - - auto *x = f.getPtr<TestA>(Channel::Pose); - REQUIRE( x ); - REQUIRE( x->a == 77 ); +/* #2.2.1 */ +TEST_CASE("ftl::data::Frame modify after flush", "[2.2.1]") { + SECTION("create fails after flush") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); - auto *y = f.getPtr<TestB>(Channel::Pose); - REQUIRE( y ); - REQUIRE( y->b == 99 ); - } + f.create<int>(Channel::Colour) = 89; + f.flush(); + + bool err = false; + try { + f.create<int>(Channel::Colour) = 90; + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + + REQUIRE( err ); + } + + SECTION("set fails after flush") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Colour) = 89; + f.flush(); + + bool err = false; + try { + f.set<int>(Channel::Colour) = 90; + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + + REQUIRE( err ); + } + + SECTION("createChange fails after flush") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Colour) = 89; + f.flush(); + + bool err = false; + try { + f.createChange<int>(Channel::Colour, ChangeType::FOREIGN) = 90; + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + + REQUIRE( err ); + } + + SECTION("channel marked readonly after flush") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Colour) = 89; + f.flush(); + REQUIRE( f.readonly(Channel::Colour) ); + } +} + +/* #2.2.2 FIXME: Specification needs review. */ + +/* #2.2.3 */ +TEST_CASE("ftl::data::Frame multiple flush", "[Frame]") { + SECTION("fail on multiple frame flush") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Colour) = 89; + f.flush(); + + bool err = false; + try { + f.flush(); + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + + REQUIRE( err ); + } +} + +/* #2.2.4 */ +TEST_CASE("ftl::data::Frame locality of changes", "[2.2.4]") { + ftl::data::make_channel<int>(Channel::Density, "density", ftl::data::StorageMode::PERSISTENT); + + SECTION("not persistent after flush only") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + f.create<int>(Channel::Density) = 44; + f.flush(); + + bool err=false; + + try { + p.get<int>(Channel::Density); + } catch(const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + } + + SECTION("not persistent without store") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + f.create<int>(Channel::Density) = 44; + + bool err=false; + + try { + p.get<int>(Channel::Density); + } catch(const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + } + + ftl::data::clearRegistry(); +} + +/* #2.2.5 */ +TEST_CASE("ftl::data::Frame changed status", "[2.2.5]") { + SECTION("change on create") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + REQUIRE( !f.changed(Channel::Pose) ); + f.create<int>(Channel::Pose) = 55; + REQUIRE( f.changed(Channel::Pose) ); + } + + SECTION("no change on untouch") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + f.create<int>(Channel::Pose) = 55; + REQUIRE( f.changed(Channel::Pose) ); + f.untouch(Channel::Pose); + REQUIRE( !f.changed(Channel::Pose) ); + } +} + +/* #2.3.1 Not applicable as a unit test of Frame. */ + +/* #2.3.2 Not applicable as a unit test of Frame. */ + +/* #2.3.3 */ +TEST_CASE("ftl::data::Frame change type", "[2.3.3]") { + SECTION("changes are local type") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + + REQUIRE( !f.changed(Channel::Pose) ); + f.create<int>(Channel::Pose) = 55; + REQUIRE( f.getChangeType(Channel::Pose) == ChangeType::LOCAL ); + } + + SECTION("local change overrides foreign change") { + Frame f = Feed::make(nullptr, 0, 0); + + f.createChange<int>(Channel::Pose, ChangeType::FOREIGN) = 55; + REQUIRE( f.getChangeType(Channel::Pose) == ChangeType::FOREIGN ); + f.store(); + + f.set<int>(Channel::Pose) = 66; + REQUIRE( f.getChangeType(Channel::Pose) == ChangeType::LOCAL ); + } + + SECTION("local change overrides completed change") { + Frame f = Feed::make(nullptr, 0, 0); + + f.createChange<int>(Channel::Pose, ChangeType::COMPLETED) = 55; + REQUIRE( f.getChangeType(Channel::Pose) == ChangeType::COMPLETED ); + f.store(); + f.set<int>(Channel::Pose) = 66; + REQUIRE( f.getChangeType(Channel::Pose) == ChangeType::LOCAL ); + } +} + +/* #2.3.4 Not applicable as a unit test of Frame. */ + +/* #2.3.5 Not applicable as a unit test of Frame. */ + +/* #2.3.6 Not applicable as a unit test of Frame. */ + +/* #2.3.7 Not applicable as a unit test of Frame. */ + +/* #3.1.1 Not applicable as a unit test of Frame. */ + +/* #3.1.2 Not applicable as a unit test of Frame. */ + +/* #3.1.3 Not applicable as a unit test of Frame. */ + +/* #3.1.4 */ +TEST_CASE("ftl::data::Frame override of persistent", "[3.1.4]") { + SECTION("local changes override persistent data") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + p.create<int>(Channel::Colour) = 44; + + // Note: Get without local create + REQUIRE( f.get<int>(Channel::Colour) == 44 ); + + // Note: set without create when exists in session store + f.set<int>(Channel::Colour) = 66; + REQUIRE( f.get<int>(Channel::Colour) == 66 ); + } +} + +/* #3.1.5 Not applicable as a unit test of Frame. */ + +/* #3.1.6 FIXME: Specification needs review */ + +/* #3.1.7 Implicit in other tests. */ + +/* #3.1.8 Not applicable as a unit test of Frame. */ + +/* #3.2.1 Not applicable as a unit test of Frame. */ + +/* #3.2.2 Not applicable as a unit test of Frame. */ + +/* #3.2.3 Not applicable as a unit test of Frame. */ + +/* #3.2.4 Not applicable as a unit test of Frame. */ + +/* #3.2.5 */ +TEST_CASE("ftl::data::Frame initial store", "[3.2.5]") { + SECTION("cannot create before store") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + bool err = false; + try { + f.create<int>(Channel::Colour) = 55; + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + } + + SECTION("can createChange before store") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.createChange<int>(Channel::Colour, ChangeType::FOREIGN) = 89; + } + + SECTION("cannot createChange after store") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.store(); + + bool err = false; + try { + f.createChange<int>(Channel::Colour, ChangeType::FOREIGN); + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + } + + SECTION("cannot store twice") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.store(); + + bool err = false; + try { + f.store(); + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + } + + SECTION("cannot flush before store") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + bool err = false; + try { + f.flush(); + } catch (const ftl::exception &e) { + e.ignore(); + err = true; + } + REQUIRE( err ); + } +} + +/* #3.3.1 Not applicable as a unit test of Frame. */ + +/* #3.3.2 Not applicable as a unit test of Frame. */ + +/* #3.3.3 See #3.2.5 */ + +/* #3.3.2 Not applicable as a unit test of Frame. However, see #3.2.5 */ + +/* #3.4.1 */ +TEST_CASE("ftl::data::Frame change events", "[3.4.1]") { + SECTION("event on store of foreign change") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + int event = 0; + auto h = f.onChange([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.createChange<int>(Channel::Pose, ChangeType::FOREIGN); + REQUIRE( event == 0 ); + + f.store(); + REQUIRE( event == 1 ); + } + + SECTION("event on store of completed change") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + int event = 0; + auto h = f.onChange([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.createChange<int>(Channel::Pose, ChangeType::COMPLETED); + REQUIRE( event == 0 ); + + f.store(); + REQUIRE( event == 1 ); + } +} + +/* #3.4.2 Not applicable as a unit test of Frame. See #3.2.5 */ + +/* #3.4.3 Not applicable as a unit test of Frame. See #3.2.5 */ + +/* #3.4.4 */ +TEST_CASE("ftl::data::Frame parallel change events", "[3.4.4]") { + SECTION("event for each of multiple changes") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + int event = 0; + auto h = f.onChange([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.createChange<int>(Channel::Pose, ChangeType::FOREIGN); + f.createChange<int>(Channel::Colour, ChangeType::FOREIGN); + f.createChange<int>(Channel::Depth, ChangeType::FOREIGN); + REQUIRE( event == 0 ); + + f.store(); + REQUIRE( event == 3 ); + } +} + +/* #3.4.5 see above test, #3.4.4 */ + +/* #3.4.6 */ +TEST_CASE("ftl::data::Frame aggregate changes", "[3.4.6]") { + ftl::data::make_channel<std::list<int>>(Channel::Density, "density", ftl::data::StorageMode::AGGREGATE); + + SECTION("multiple changes cause single event") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + int event = 0; + auto h = f.onChange([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {34}; + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {55}; + f.createChange<std::list<int>>(Channel::Density, ChangeType::FOREIGN) = {12,89}; + REQUIRE( event == 0 ); + + f.store(); + REQUIRE( event == 1 ); + } + + ftl::data::clearRegistry(); +} + +/* #3.4.7 */ +//static_assert(std::is_same<decltype(Frame::onChange),ftl::Handle(ftl::codecs::Channel, const std::function<bool(Frame&,ftl::codecs::Channel)> &)>::value, "Wrong event handler type"); + +/* #3.4.8 Not applicable as a unit test of Frame. */ + +/* #4.1.1 */ +TEST_CASE("ftl::data::Frame flush events", "[4.1.1]") { + SECTION("event on flush") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + int event = 0; + auto h = f.onFlush([&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") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + int event = 0; + auto h = p.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.create<int>(Channel::Pose) = 55; + REQUIRE( event == 0 ); + + f.flush(); + REQUIRE( event == 1 ); + } +} + +/* #4.1.2 */ +TEST_CASE("ftl::data::Frame flush per channel", "[4.1.2]") { + SECTION("event on flush of channel") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + int event = 0; + auto h = f.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.create<int>(Channel::Pose) = 55; + f.create<int>(Channel::Colour) = 45; + REQUIRE( event == 0 ); + + f.flush(Channel::Pose); + REQUIRE( event == 1 ); + } + + SECTION("flushed channel readonly") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + f.create<int>(Channel::Pose) = 55; + f.create<int>(Channel::Colour) = 45; + + f.flush(Channel::Pose); + REQUIRE( f.readonly(Channel::Pose) ); + REQUIRE( !f.readonly(Channel::Colour) ); + } +} + +/* #4.1.3 Not applicable as a unit test of Frame. */ + +/* #4.1.4 Not applicable as a unit test of Frame. */ + +/* #4.1.5 Not applicable as a unit test of Frame. */ + +/* #4.1.6 */ +TEST_CASE("ftl::data::Frame flush on destruct", "[4.1.6]") { + SECTION("flush a non-flushed frame on destruct") { + Session p; + + int event = 0; + auto h = p.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + { + Frame f = Feed::make(&p, 0, 0); + f.store(); + f.create<int>(Channel::Pose) = 55; + REQUIRE( event == 0 ); + } + + REQUIRE( event == 1 ); + } + + SECTION("no flush of flushed frame on destruct") { + Session p; + + int event = 0; + auto h = p.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + { + Frame f = Feed::make(&p, 0, 0); + f.store(); + f.create<int>(Channel::Pose) = 55; + f.flush(); + REQUIRE( event == 1 ); + } + + REQUIRE( event == 1 ); + } +} + +/* #4.2.1 */ +TEST_CASE("ftl::data::Frame flush foreign", "[4.2.1]") { + // For local flush see #4.1.1 + + SECTION("event on foreign flush") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.createChange<int>(Channel::Colour, ChangeType::FOREIGN) = 55; + f.store(); + + int event = 0; + auto h = f.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + REQUIRE( event == 0 ); + f.flush(); + REQUIRE( event == 1 ); + } +} + +/* #4.2.2 */ +TEST_CASE("ftl::data::Frame no flush of completed", "[4.2.2]") { + // For local flush see #4.1.1 + + SECTION("no event on completed flush") { + Session p; + Frame f = Feed::make(&p, 0, 0); + + f.createChange<int>(Channel::Colour, ChangeType::COMPLETED) = 55; + f.store(); + + int event = 0; + auto h = f.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + REQUIRE( event == 0 ); + f.flush(); + REQUIRE( event == 0 ); + } +} + +/* #4.2.3 see #2.2.4 */ + +/* #4.3.1 see #4.2.1 */ + +/* #4.3.2 see #4.2.2 but also Feed class */ + +/* #4.3.3 see #2.2.4 */ + +/* #4.4.1 see #4.1.6 and #4.2.1 */ + +/* #4.4.2 see #4.2.2 */ + +/* #4.4.3 */ +TEST_CASE("ftl::data::Frame parallel flush events", "[4.4.3]") { + SECTION("event for each of multiple changes") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + int event = 0; + auto h = f.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.create<int>(Channel::Pose); + f.create<int>(Channel::Colour); + f.create<int>(Channel::Depth); + REQUIRE( event == 0 ); + + f.flush(); + REQUIRE( event == 3 ); + } +} + +/* #4.4.4 see #4.4.3 */ + +/* #4.4.5 */ +TEST_CASE("ftl::data::Frame aggregate flush events", "[4.4.5]") { + ftl::data::make_channel<std::list<int>>(Channel::Density, "density", ftl::data::StorageMode::AGGREGATE); + + SECTION("multiple changes cause single event") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + int event = 0; + auto h = f.onFlush([&event](Frame &frame, Channel c) { + event++; + return true; + }); + + f.create<std::list<int>>(Channel::Density) = {34}; + f.create<std::list<int>>(Channel::Density) = {55}; + f.create<std::list<int>>(Channel::Density) = {12,89}; + REQUIRE( event == 0 ); + + f.flush(); + REQUIRE( event == 1 ); + } + + ftl::data::clearRegistry(); +} + +/* #4.4.6 */ +// TODO: Check function signature + +/* #4.4.7 FIXME: Review specification */ +TEST_CASE("ftl::data::Frame status after flush", "[4.4.7]") { + SECTION("still changed after flush") { + Session p; + Frame f = Feed::make(&p, 0, 0); + f.store(); + + f.create<int>(Channel::Colour) = 55; + f.flush(); + + REQUIRE( f.changed(Channel::Colour) ); + } +} + +/* #5 FIXME: RPC not implemented. */ + +/* #6 See pool unit tests */ + + + +// ==== Complex type overload test ============================================= + +struct TestA { + int a=55; +}; + +struct TestB { + int b=99; +}; + +struct TestC { + TestA a; + TestB b; +}; + +template <> +TestA &ftl::data::Frame::create<TestA>(ftl::codecs::Channel c) { + return create<TestC>(c).a; +} + +template <> +TestB &ftl::data::Frame::create<TestB>(ftl::codecs::Channel c) { + return create<TestC>(c).b; +} + +template <> +const TestA *ftl::data::Frame::getPtr<TestA>(ftl::codecs::Channel c) const noexcept { + auto *ptr = getPtr<TestC>(c); + return (ptr) ? &ptr->a : nullptr; +} + +template <> +const TestB *ftl::data::Frame::getPtr<TestB>(ftl::codecs::Channel c) const noexcept { + auto *ptr = getPtr<TestC>(c); + return (ptr) ? &ptr->b : nullptr; +} + +TEST_CASE("ftl::data::Frame Complex Overload", "[Frame]") { + ftl::data::make_channel<TestC>(Channel::Pose, "pose", ftl::data::StorageMode::PERSISTENT); + + SECTION("Create and get first type with default") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + f.create<TestA>(Channel::Pose); + + auto *x = f.getPtr<TestA>(Channel::Pose); + REQUIRE( x ); + REQUIRE( x->a == 55 ); + + auto *y = f.getPtr<TestB>(Channel::Pose); + REQUIRE( y ); + REQUIRE( y->b == 99 ); + } + + SECTION("Create and get first type with value") { + Frame f = Feed::make(nullptr, 0, 0); + f.store(); + f.create<TestA>(Channel::Pose) = {77}; + + auto *x = f.getPtr<TestA>(Channel::Pose); + REQUIRE( x ); + REQUIRE( x->a == 77 ); + + auto *y = f.getPtr<TestB>(Channel::Pose); + REQUIRE( y ); + REQUIRE( y->b == 99 ); + } + + ftl::data::clearRegistry(); } diff --git a/components/structures/test/pool_unit.cpp b/components/structures/test/pool_unit.cpp new file mode 100644 index 000000000..fd96db8c8 --- /dev/null +++ b/components/structures/test/pool_unit.cpp @@ -0,0 +1,120 @@ +/* + * These tests directly relate to the specification found at: + * https://gitlab.utu.fi/nicolas.pope/ftl/-/wikis/Design/Frames + * + * Starting from section 5 on memory management. + */ + +#include "catch.hpp" + +#include <ftl/data/framepool.hpp> + +using ftl::data::Session; +using ftl::data::Frame; +using ftl::data::Pool; +using ftl::codecs::Channel; +using ftl::data::ChangeType; +using ftl::data::StorageMode; +using ftl::data::FrameStatus; + +/* #5.1 */ +TEST_CASE("ftl::data::Pool create frames", "[5.1]") { + SECTION("can allocate valid frame from pool") { + Pool pool(5,5); + + Frame f = pool.allocate(0, 0); + REQUIRE( f.status() == FrameStatus::CREATED ); + REQUIRE( pool.size() == 4 ); + } +} + +/* #5.2 */ +TEST_CASE("ftl::data::Pool release frames on destruct", "[5.1]") { + SECTION("can destroy allocated frame") { + Pool pool(5,5); + + { + Frame f = pool.allocate(0, 0); + REQUIRE( f.status() == FrameStatus::CREATED ); + REQUIRE( pool.size() == 4 ); + } + + REQUIRE( pool.size() == 5 ); + } + + SECTION("data reused between allocations") { + Pool pool(1,1); + + const int *ptr = nullptr; + + { + Frame f = pool.allocate(0, 0); + f.store(); + f.create<std::vector<int>>(Channel::Colour) = {44,55,66}; + ptr = f.get<std::vector<int>>(Channel::Colour).data(); + } + + REQUIRE( pool.size() == 1 ); + + { + Frame f = pool.allocate(0, 0); + f.store(); + auto &v = f.create<std::vector<int>>(Channel::Colour); + + REQUIRE( v[0] == 44 ); + REQUIRE( v[1] == 55 ); + REQUIRE( v[2] == 66 ); + + REQUIRE( (ptr && ptr == v.data()) ); + } + } +} + +/* #5.3 */ +TEST_CASE("ftl::data::Pool reused frames are stale", "[5.3]") { + SECTION("data reused is stale") { + Pool pool(1,1); + + { + Frame f = pool.allocate(0, 0); + f.store(); + f.create<std::vector<int>>(Channel::Colour) = {44,55,66}; + } + + REQUIRE( pool.size() == 1 ); + + { + Frame f = pool.allocate(0, 0); + f.store(); + + REQUIRE( !f.has(Channel::Colour) ); + REQUIRE( !f.changed(Channel::Colour) ); + + auto &v = f.create<std::vector<int>>(Channel::Colour); + REQUIRE( v[0] == 44 ); + } + } +} + +/* #5.4 */ +// Hard to test + +/* #5.5 */ +TEST_CASE("ftl::data::Pool excessive allocations", "[5.5]") { + SECTION("allocate far beyond pool size") { + Pool pool(10,20); + + { + std::list<Frame> l; + for (int i=0; i<100; ++i) { + l.push_back(std::move(pool.allocate(0,0))); + } + + REQUIRE( pool.size() >= 10 ); + } + + // 2*pool size is the chosen max + REQUIRE( pool.size() <= 20 ); + } +} + diff --git a/lib/libsgm/src/CMakeLists.txt b/lib/libsgm/src/CMakeLists.txt index a338f8380..d1bc459e4 100644 --- a/lib/libsgm/src/CMakeLists.txt +++ b/lib/libsgm/src/CMakeLists.txt @@ -5,6 +5,8 @@ find_package(CUDA REQUIRED) include_directories(../include) if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CUDA_HOST_COMPILER gcc-7) + set(CUDA_HOST_COMPILER gcc-7) set(CMAKE_CXX_FLAGS "-O3 -Wall -fPIC") set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -std=c++11") endif() diff --git a/lib/libstereo/CMakeLists.txt b/lib/libstereo/CMakeLists.txt index 64e19cd84..cc1b10aef 100644 --- a/lib/libstereo/CMakeLists.txt +++ b/lib/libstereo/CMakeLists.txt @@ -20,6 +20,7 @@ set(CMAKE_USE_RELATIVE_PATHS ON) set(CMAKE_CXX_FLAGS_RELEASE) if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CUDA_HOST_COMPILER gcc-7) set(CMAKE_CUDA_FLAGS "--gpu-architecture=compute_61 -std=c++14 -Xcompiler -fPIC -Xcompiler ${OpenMP_CXX_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "-O3") else() -- GitLab