diff --git a/components/rgbd-sources/src/sources/screencapture/screencapture.cpp b/components/rgbd-sources/src/sources/screencapture/screencapture.cpp index 6ade3f14ecc1cd858d3597d17f3308eebae722eb..fef465c2067120c55641828632c6ee8190c2bd8c 100644 --- a/components/rgbd-sources/src/sources/screencapture/screencapture.cpp +++ b/components/rgbd-sources/src/sources/screencapture/screencapture.cpp @@ -216,6 +216,8 @@ bool ScreenCapture::retrieve(ftl::rgbd::Frame &frame) { if (!ready_) return false; cv::Mat img; + // TODO: Proper, press, release and motion behaviour + // Also, render the cursor location if (host_->value("enable_touch", false)) { if (frame.changed(Channel::Touch)) { const auto &touches = frame.get<std::vector<ftl::codecs::Touch>>(Channel::Touch); diff --git a/components/structures/include/ftl/data/frame.hpp b/components/structures/include/ftl/data/frame.hpp deleted file mode 100644 index c304e4e97b87eb310e1e1912f17cb680cb9d1e28..0000000000000000000000000000000000000000 --- a/components/structures/include/ftl/data/frame.hpp +++ /dev/null @@ -1,567 +0,0 @@ -#pragma once -#ifndef _FTL_DATA_FRAME_HPP_ -#define _FTL_DATA_FRAME_HPP_ - -#include <ftl/configuration.hpp> -#include <ftl/exception.hpp> - -#include <ftl/codecs/channels.hpp> -#include <ftl/codecs/codecs.hpp> -//#include <ftl/codecs/packet.hpp> -#include <ftl/utility/vectorbuffer.hpp> - -#include <type_traits> -#include <array> -//#include <list> -#include <unordered_map> - -#include <Eigen/Eigen> - -namespace ftl { -namespace data { - -/** - * Manage a set of channels corresponding to a single frame. There are three - * kinds of channel in a frame: 1) the data type of interest (DoI) - * (eg. audio, video, etc), 2) Persistent state and 3) Generic meta data. - * The DoI is a template arg and could be in any form. Different DoIs will use - * different frame instances, ie. audio and video frame types. Persistent state - * may or may not change between frames but is always available. Finally, - * generic data is a small amount of information about the primary data that may - * or may not exist each frame, and is not required to exist. - * - * There is no specification for frame rates, intervals or synchronisation at - * this level. A frame is a quantum of data of any temporal size which can be - * added to a FrameSet to be synchronised with other frames. - * - * Use this template class either by inheriting it or just by providing the - * template arguments. It is not abstract and can work directly. - * - * The template DATA parameter must be a class or struct that implements three - * methods: 1) `const T& at<T>()` to cast to const type, 2) `T& at<T>()` to cast - * to non-const type, and 3) `T& make<T>() to create data as a type. - * - * The STATE parameter must be an instance of `ftl::data::FrameState`. - * - * @see ftl::data::FrameState - * @see ftl::data::FrameSet - * @see ftl::rgbd::FrameState - * @see ftl::rgbd::Frame - */ -template <int BASE, int N, typename STATE, typename DATA> -class Frame { - static_assert(N <= ftl::codecs::Channels<BASE>::kMax, "Too many channels requested"); - -public: - Frame() : origin_(nullptr) {} - Frame(Frame &&f) { - f.swapTo(*this); - f.reset(); - } - - Frame &operator=(Frame &&f) { - f.swapTo(*this); - f.reset(); - return *this; - } - - // Prevent frame copy, instead use a move. - Frame(const Frame &)=delete; - Frame &operator=(const Frame &)=delete; - - /** - * Perform a buffer swap of the selected channels. This is intended to be - * a copy from `this` to the passed frame object but by buffer swap - * instead of memory copy, meaning `this` may become invalid afterwards. - * It is a complete frame swap. - */ - void swapTo(ftl::codecs::Channels<BASE>, Frame &); - - void swapTo(Frame &); - - /** - * Swap only selected channels to another frame, without resetting or swapping - * any other aspect of the frame. Unlike swapTo, this isn't intended to - * be a complete frame swap. - */ - void swapChannels(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &); - - void swapChannels(ftl::codecs::Channel, ftl::codecs::Channel); - - /** - * Does a host or device memory copy into the given frame. - */ - void copyTo(ftl::codecs::Channels<BASE>, Frame &); - - /** - * Create a channel but without any format. - */ - template <typename T> T &create(ftl::codecs::Channel c); - - /** - * Set the value of a channel. Some channels should not be modified via the - * non-const get method, for example the data channels. - */ - template <typename T> void create(ftl::codecs::Channel channel, const T &value); - - /** - * Append encoded data for a channel. This will move the data, invalidating - * the original packet structure. It is to be used to allow data that is - * already encoded to be transmitted or saved again without re-encoding. - * A called to `create` will clear all encoded data for that channel. - */ - //void pushPacket(ftl::codecs::Channel c, ftl::codecs::Packet &pkt); - - /** - * Obtain a list of any existing encodings for this channel. - */ - //const std::list<ftl::codecs::Packet> &getPackets(ftl::codecs::Channel c) const; - - /** - * Clear any existing encoded packets. Used when the channel data is - * modified and the encodings are therefore out-of-date. - */ - //void clearPackets(ftl::codecs::Channel c); - - /** - * Reset all channels without releasing memory. - */ - void reset(); - - /** - * Reset all channels and release memory. - */ - //void resetFull(); - - /** - * Is there valid data in channel (either host or gpu). This does not - * verify that any memory or data exists for the channel. - */ - bool hasChannel(ftl::codecs::Channel channel) const { - int c = static_cast<int>(channel); - if (c >= 64 && c <= 68) return true; - else if (c >= 2048) return data_channels_.has(channel); - else if (c < BASE || c >= BASE+N) return false; - else return channels_.has(channel); - } - - /** - * Obtain a mask of all available channels in the frame. - */ - inline ftl::codecs::Channels<BASE> getChannels() const { return channels_; } - - inline ftl::codecs::Channels<2048> getDataChannels() const { return data_channels_; } - - /** - * Does this frame have new data for a channel. This is compared with a - * previous frame and always returns true for image data. It may return - * false for persistent state data (calibration, pose etc). - */ - inline bool hasChanged(ftl::codecs::Channel c) const { - return (static_cast<int>(c) < 64) ? true : state_.hasChanged(c); - } - - /** - * Method to get reference to the channel content. - * @param Channel type - * @return Const reference to channel data - * - * Result is valid only if hasChannel() is true. Host/Gpu transfer is - * performed, if necessary, but with a warning since an explicit upload or - * download should be used. - */ - template <typename T> const T& get(ftl::codecs::Channel channel) const; - - /** - * Get the data from a data channel. This only works for the data channels - * and will throw an exception with any others. - */ - template <typename T> void get(ftl::codecs::Channel channel, T ¶ms) const; - - /** - * Method to get reference to the channel content. The channel must already - * have been created of this will throw an exception. See `getBuffer` to - * get access before creation. - * - * @param Channel type - * @return Reference to channel data - * - * Result is valid only if hasChannel() is true. - */ - template <typename T> T& get(ftl::codecs::Channel channel); - - /** - * Method to get reference to the channel content. Unlike `get`, the channel - * must not already exist as this is intended as a pre-create step that - * allocates memory and populates the buffer. `create` must then be called - * to make the channel available. - * - * @param Channel type - * @return Reference to channel data - * - * Result is valid only if hasChannel() is true. - */ - template <typename T> T& getBuffer(ftl::codecs::Channel channel); - - /** - * Wrapper accessor function to get frame pose. - */ - const Eigen::Matrix4d &getPose() const; - - /** - * Change the pose of the origin state and mark as changed. - */ - void setPose(const Eigen::Matrix4d &pose, bool mark=true); - - /** - * Change the pose of the origin state and mark as changed. - */ - void patchPose(const Eigen::Matrix4d &pose); - - /** - * Wrapper to access left settings channel. - */ - const typename STATE::Settings &getSettings() const; - - const typename STATE::Settings &getLeft() const; - const typename STATE::Settings &getRight() const; - - void setLeft(const typename STATE::Settings &); - void setRight(const typename STATE::Settings &); - - /** - * Change left settings in the origin state. This should send - * the changed parameters in reverse through a stream. - */ - void setSettings(const typename STATE::Settings &c); - - /** - * Dump the current frame config object to a json string. - */ - std::string getConfigString() const; - - /** - * Access the raw data channel vector object. - */ - const std::vector<unsigned char> &getRawData(ftl::codecs::Channel c) const; - - /** - * Provide raw data for a data channel. - */ - void createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v); - - /** - * Wrapper to access a config property. If the property does not exist or - * is not of the requested type then the returned optional is false. - */ - template <class T> - std::optional<T> get(const std::string &name) { return state_.template get<T>(name); } - - /** - * Modify a config property. This does not modify the origin config so - * will not get transmitted over the stream. - * @todo Modify origin to send backwards over a stream. - */ - template <typename T> - void set(const std::string &name, T value) { state_.set(name, value); } - - /** - * Set the persistent state for the frame. This can only be done after - * construction or a reset. Multiple calls to this otherwise will throw - * an exception. The pointer must remain valid for the life of the frame. - */ - void setOrigin(STATE *state); - - /** - * Get the original frame state object. This can be a nullptr in some rare - * cases. When wishing to change state (pose, calibration etc) then those - * changes must be done on this origin, either directly or via wrappers. - */ - STATE *origin() const { return origin_; } - - //ftl::codecs::Channels<BASE> completed; - - typedef STATE State; - - int id; - -protected: - /* Lookup internal state for a given channel. */ - inline DATA &getData(ftl::codecs::Channel c) { return data_[static_cast<unsigned int>(c)-BASE]; } - inline const DATA &getData(ftl::codecs::Channel c) const { return data_[static_cast<unsigned int>(c)-BASE]; } - -private: - std::array<DATA, N> data_; - - std::unordered_map<int, std::vector<unsigned char>> data_data_; - - ftl::codecs::Channels<BASE> channels_; // Does it have a channel - ftl::codecs::Channels<2048> data_channels_; - - // Persistent state - STATE state_; - STATE *origin_; -}; - -} -} - -// ==== Implementations ======================================================== - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::reset() { - origin_ = nullptr; - channels_.clear(); - data_channels_.clear(); - for (size_t i=0u; i<ftl::codecs::Channels<BASE>::kMax; ++i) { - data_[i].reset(); - } -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::swapTo(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &f) { - f.reset(); - f.origin_ = origin_; - f.state_ = state_; - - // For all channels in this frame object - for (auto c : channels_) { - // Should we swap this channel? - if (channels.has(c)) { - f.channels_ += c; - // TODO: Make sure this does a move not copy - std::swap(f.getData(c),getData(c)); - } - } - - f.data_data_ = std::move(data_data_); - f.data_channels_ = data_channels_; - data_channels_.clear(); - channels_.clear(); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::swapTo(Frame<BASE,N,STATE,DATA> &f) { - swapTo(ftl::codecs::Channels<BASE>::All(), f); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::swapChannels(ftl::codecs::Channel a, ftl::codecs::Channel b) { - auto &m1 = getData(a); - auto &m2 = getData(b); - - auto temp = std::move(m2); - m2 = std::move(m1); - m1 = std::move(temp); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::swapChannels(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &f) { - // For all channels in this frame object - for (auto c : channels_) { - // Should we swap this channel? - if (channels.has(c)) { - f.channels_ += c; - // TODO: Make sure this does a move not copy - std::swap(f.getData(c),getData(c)); - channels_ -= c; - } - } -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::copyTo(ftl::codecs::Channels<BASE> channels, Frame<BASE,N,STATE,DATA> &f) { - f.reset(); - f.origin_ = origin_; - f.state_ = state_; - - // For all channels in this frame object - for (auto c : channels_) { - // Should we copy this channel? - if (channels.has(c)) { - f.channels_ += c; - f.getData(c) = getData(c); - } - } - - f.data_data_ = data_data_; - f.data_channels_ = data_channels_; -} - -template <int BASE, int N, typename STATE, typename DATA> -// cppcheck-suppress * -template <typename T> -T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel) { - if (channel == ftl::codecs::Channel::None) { - throw FTL_Error("Attempting to get channel 'None'"); - } - - // Add channel if not already there - if (!channels_.has(channel)) { - throw FTL_Error("Frame channel does not exist: " << (int)channel); - } - - return getData(channel).template as<T>(); -} - -template <int BASE, int N, typename STATE, typename DATA> -// cppcheck-suppress * -template <typename T> -T& ftl::data::Frame<BASE,N,STATE,DATA>::getBuffer(ftl::codecs::Channel channel) { - if (channel == ftl::codecs::Channel::None) { - throw ftl::exception("Attempting to get channel 'None'"); - } - - if (channels_.has(channel)) { - throw ftl::exception(ftl::Formatter() << "Cannot getBuffer on existing channel: " << (int)channel); - } - - if (static_cast<int>(channel) < BASE || static_cast<int>(channel) >= BASE+32) { - throw ftl::exception(ftl::Formatter() << "Frame channel does not exist: " << (int)channel); - } - - return getData(channel).template make<T>(); -} - -template <int BASE, int N, typename STATE, typename DATA> -// cppcheck-suppress * -template <typename T> -const T& ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel) const { - if (channel == ftl::codecs::Channel::None) { - throw FTL_Error("Attempting to get channel 'None'"); - } else if (channel == ftl::codecs::Channel::Pose) { - return state_.template as<T,ftl::codecs::Channel::Pose>(); - } else if (channel == ftl::codecs::Channel::Calibration) { - return state_.template as<T,ftl::codecs::Channel::Calibration>(); - } else if (channel == ftl::codecs::Channel::Calibration2) { - return state_.template as<T,ftl::codecs::Channel::Calibration2>(); - } else if (channel == ftl::codecs::Channel::Configuration) { - return state_.template as<T,ftl::codecs::Channel::Configuration>(); - } - - // Add channel if not already there - if (!channels_.has(channel)) { - throw FTL_Error("Frame channel does not exist: " << (int)channel); - } - - return getData(channel).template as<T>(); -} - -// Default data channel implementation -template <int BASE, int N, typename STATE, typename DATA> -// cppcheck-suppress * -template <typename T> -void ftl::data::Frame<BASE,N,STATE,DATA>::get(ftl::codecs::Channel channel, T ¶ms) const { - if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel"); - if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist"); - - const auto &i = data_data_.find(static_cast<int>(channel)); - if (i == data_data_.end()) throw FTL_Error("Data channel does not exist"); - - auto unpacked = msgpack::unpack((const char*)(*i).second.data(), (*i).second.size()); - unpacked.get().convert(params); -} - -template <int BASE, int N, typename STATE, typename DATA> -// cppcheck-suppress * -template <typename T> -T &ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel c) { - if (c == ftl::codecs::Channel::None) { - throw FTL_Error("Cannot create a None channel"); - } - channels_ += c; - - auto &m = getData(c); - return m.template make<T>(); -} - -template <int BASE, int N, typename STATE, typename DATA> -// cppcheck-suppress * -template <typename T> -void ftl::data::Frame<BASE,N,STATE,DATA>::create(ftl::codecs::Channel channel, const T &value) { - if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel"); - - data_channels_ += channel; - - auto &v = *std::get<0>(data_data_.insert({static_cast<int>(channel),{}})); - v.second.resize(0); - ftl::util::FTLVectorBuffer buf(v.second); - msgpack::pack(buf, value); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::setOrigin(STATE *state) { - if (origin_ != nullptr) { - throw FTL_Error("Can only set origin once after reset"); - } - - origin_ = state; - state_ = *state; -} - -template <int BASE, int N, typename STATE, typename DATA> -const Eigen::Matrix4d &ftl::data::Frame<BASE,N,STATE,DATA>::getPose() const { - return get<Eigen::Matrix4d>(ftl::codecs::Channel::Pose); -} - -template <int BASE, int N, typename STATE, typename DATA> -const typename STATE::Settings &ftl::data::Frame<BASE,N,STATE,DATA>::getLeft() const { - return get<typename STATE::Settings>(ftl::codecs::Channel::Calibration); -} - -template <int BASE, int N, typename STATE, typename DATA> -const typename STATE::Settings &ftl::data::Frame<BASE,N,STATE,DATA>::getSettings() const { - return get<typename STATE::Settings>(ftl::codecs::Channel::Calibration); -} - -template <int BASE, int N, typename STATE, typename DATA> -const typename STATE::Settings &ftl::data::Frame<BASE,N,STATE,DATA>::getRight() const { - return get<typename STATE::Settings>(ftl::codecs::Channel::Calibration2); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::setPose(const Eigen::Matrix4d &pose, bool mark) { - if (origin_) { - if (mark) origin_->setPose(pose); - else origin_->getPose() = pose; - } - state_.setPose(pose); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::patchPose(const Eigen::Matrix4d &pose) { - state_.getPose() = pose * state_.getPose(); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::setLeft(const typename STATE::Settings &c) { - if (origin_) origin_->setLeft(c); - state_.setLeft(c); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::setRight(const typename STATE::Settings &c) { - if (origin_) origin_->setRight(c); - state_.setRight(c); -} - -template <int BASE, int N, typename STATE, typename DATA> -std::string ftl::data::Frame<BASE,N,STATE,DATA>::getConfigString() const { - return ftl::config::dumpJSON(get<nlohmann::json>(ftl::codecs::Channel::Configuration)); -} - -template <int BASE, int N, typename STATE, typename DATA> -const std::vector<unsigned char> &ftl::data::Frame<BASE,N,STATE,DATA>::getRawData(ftl::codecs::Channel channel) const { - if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Non data channel"); - if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist"); - - return data_data_.at(static_cast<int>(channel)); -} - -template <int BASE, int N, typename STATE, typename DATA> -void ftl::data::Frame<BASE,N,STATE,DATA>::createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v) { - data_data_.insert({static_cast<int>(c), v}); - data_channels_ += c; -} - -#endif // _FTL_DATA_FRAME_HPP_ diff --git a/components/structures/include/ftl/data/frameset.hpp b/components/structures/include/ftl/data/frameset.hpp deleted file mode 100644 index 4de7035f584e8d26490b799b3bd1846c25486fc3..0000000000000000000000000000000000000000 --- a/components/structures/include/ftl/data/frameset.hpp +++ /dev/null @@ -1,244 +0,0 @@ -#ifndef _FTL_DATA_FRAMESET_HPP_ -#define _FTL_DATA_FRAMESET_HPP_ - -#include <ftl/threads.hpp> -#include <ftl/timer.hpp> -#include <ftl/data/frame.hpp> -#include <functional> - -//#include <opencv2/opencv.hpp> -#include <vector> - -namespace ftl { -namespace data { - -// Allows a latency of 20 frames maximum -//static const size_t kMaxFramesets = 15; -static const size_t kMaxFramesInSet = 32; - -enum class FSFlag : int { - STALE = 0, - PARTIAL = 1 -}; - -/** - * Represents a set of synchronised frames, each with two channels. This is - * used to collect all frames from multiple computers that have the same - * timestamp. - */ -template <typename FRAME> -class FrameSet { - public: - - int id=0; - int64_t timestamp; // Millisecond timestamp of all frames - int64_t originClockDelta; - std::vector<FRAME> frames; - std::atomic<int> count; // Number of valid frames - std::atomic<unsigned int> mask; // Mask of all sources that contributed - //bool stale; // True if buffers have been invalidated - SHARED_MUTEX mtx; - - Eigen::Matrix4d pose; // Set to identity by default. - - inline int64_t localTimestamp() const { return timestamp + originClockDelta; } - - void set(FSFlag f) { flags_ |= (1 << static_cast<int>(f)); } - void clear(FSFlag f) { flags_ &= ~(1 << static_cast<int>(f)); } - bool test(FSFlag f) const { return flags_ & (1 << static_cast<int>(f)); } - void clearFlags() { flags_ = 0; } - - /** - * Move the entire frameset to another frameset object. This will - * invalidate the current frameset object as all memory buffers will be - * moved. - */ - void swapTo(ftl::data::FrameSet<FRAME> &); - - typedef FRAME Frame; - typedef std::function<bool(ftl::data::FrameSet<FRAME> &)> Callback; - - /** - * Get the data from a data channel. This only works for the data channels - * and will throw an exception with any others. - */ - template <typename T> void get(ftl::codecs::Channel channel, T ¶ms) const; - - /** - * Set the value of a channel. Some channels should not be modified via the - * non-const get method, for example the data channels. - */ - template <typename T> void create(ftl::codecs::Channel channel, const T &value); - - /** - * Access the raw data channel vector object. - */ - const std::vector<unsigned char> &getRawData(ftl::codecs::Channel c) const; - - /** - * Provide raw data for a data channel. - */ - void createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v); - - /** - * Is there valid data in channel (either host or gpu). This does not - * verify that any memory or data exists for the channel. - */ - inline bool hasChannel(ftl::codecs::Channel channel) const { - int c = static_cast<int>(channel); - if (c == 66) return true; - else if (c >= 2048) return data_channels_.has(channel); - return false; - } - - /** - * Check that a given frame is valid in this frameset. - */ - inline bool hasFrame(size_t ix) const { return (1 << ix) & mask; } - - /** - * Get the first valid frame in this frameset. No valid frames throws an - * exception. - */ - FRAME &firstFrame(); - - const FRAME &firstFrame() const; - - void clearData() { - data_.clear(); - data_channels_.clear(); - } - - ftl::codecs::Channels<2048> getDataChannels() const { return data_channels_; } - - private: - std::unordered_map<int, std::vector<unsigned char>> data_; - ftl::codecs::Channels<2048> data_channels_; - std::atomic<int> flags_; -}; - -/** - * Callback type for receiving video frames. - */ -//typedef std::function<bool(ftl::rgbd::FrameSet &)> VideoCallback; - -/** - * Abstract class for any generator of FrameSet structures. A generator - * produces (decoded) frame sets at regular frame intervals depending on the - * global timer settings. The `onFrameSet` callback may be triggered from any - * thread and also may drop frames and not be called for a given timestamp. - */ -template <typename FRAMESET> -class Generator { - public: - Generator() {} - virtual ~Generator() {} - - /** Number of frames in last frameset. This can change over time. */ - virtual size_t size()=0; - - /** - * Get the persistent state object for a frame. An exception is thrown - * for a bad index. - */ - virtual typename FRAMESET::Frame::State &state(size_t ix)=0; - - inline typename FRAMESET::Frame::State &operator[](int ix) { return state(ix); } - - /** Register a callback to receive new frame sets. */ - virtual void onFrameSet(const typename FRAMESET::Callback &)=0; -}; - -} -} - -// === Implementations ========================================================= - -template <typename FRAME> -void ftl::data::FrameSet<FRAME>::swapTo(ftl::data::FrameSet<FRAME> &fs) { - //UNIQUE_LOCK(fs.mtx, lk); - std::unique_lock<std::shared_mutex> lk(fs.mtx); - - //if (fs.frames.size() != frames.size()) { - // Assume "this" is correct and "fs" is not. - fs.frames.resize(frames.size()); - //} - - fs.timestamp = timestamp; - fs.count = static_cast<int>(count); - fs.flags_ = (int)flags_; - fs.mask = static_cast<unsigned int>(mask); - fs.id = id; - fs.pose = pose; - - for (size_t i=0; i<frames.size(); ++i) { - frames[i].swapTo(ftl::codecs::Channels<0>::All(), fs.frames[i]); - } - - std::swap(fs.data_, data_); - fs.data_channels_ = data_channels_; - data_channels_.clear(); - - set(ftl::data::FSFlag::STALE); -} - -// Default data channel implementation -template <typename FRAME> -// cppcheck-suppress * -template <typename T> -void ftl::data::FrameSet<FRAME>::get(ftl::codecs::Channel channel, T ¶ms) const { - if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel"); - if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist"); - - const auto &i = data_.find(static_cast<int>(channel)); - if (i == data_.end()) throw FTL_Error("Data channel does not exist"); - - auto unpacked = msgpack::unpack((const char*)(*i).second.data(), (*i).second.size()); - unpacked.get().convert(params); -} - -template <typename FRAME> -// cppcheck-suppress * -template <typename T> -void ftl::data::FrameSet<FRAME>::create(ftl::codecs::Channel channel, const T &value) { - if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Cannot use generic type with non data channel"); - - data_channels_ += channel; - - auto &v = *std::get<0>(data_.insert({static_cast<int>(channel),{}})); - v.second.resize(0); - ftl::util::FTLVectorBuffer buf(v.second); - msgpack::pack(buf, value); -} - -template <typename FRAME> -const std::vector<unsigned char> &ftl::data::FrameSet<FRAME>::getRawData(ftl::codecs::Channel channel) const { - if (static_cast<int>(channel) < static_cast<int>(ftl::codecs::Channel::Data)) throw FTL_Error("Non data channel"); - if (!hasChannel(channel)) throw FTL_Error("Data channel does not exist"); - - return data_.at(static_cast<int>(channel)); -} - -template <typename FRAME> -void ftl::data::FrameSet<FRAME>::createRawData(ftl::codecs::Channel c, const std::vector<unsigned char> &v) { - data_.insert({static_cast<int>(c), v}); - data_channels_ += c; -} - -template <typename FRAME> -FRAME &ftl::data::FrameSet<FRAME>::firstFrame() { - for (size_t i=0; i<frames.size(); ++i) { - if (hasFrame(i)) return frames[i]; - } - throw FTL_Error("No frames in frameset"); -} - -template <typename FRAME> -const FRAME &ftl::data::FrameSet<FRAME>::firstFrame() const { - for (size_t i=0; i<frames.size(); ++i) { - if (hasFrame(i)) return frames[i]; - } - throw FTL_Error("No frames in frameset"); -} - -#endif // _FTL_DATA_FRAMESET_HPP_ diff --git a/components/structures/include/ftl/data/framestate.hpp b/components/structures/include/ftl/data/framestate.hpp deleted file mode 100644 index 378a37f3d49406c9dec46114e04da8c1e6cbc21d..0000000000000000000000000000000000000000 --- a/components/structures/include/ftl/data/framestate.hpp +++ /dev/null @@ -1,302 +0,0 @@ -#ifndef _FTL_DATA_FRAMESTATE_HPP_ -#define _FTL_DATA_FRAMESTATE_HPP_ - -#include <ftl/configuration.hpp> -#include <ftl/exception.hpp> -#include <ftl/codecs/channels.hpp> -#include <Eigen/Eigen> -#include <array> -#include <optional> -#include <string> - -namespace ftl { -namespace data { - -/** - * Represent state that is persistent across frames. Such state may or may not - * change from one frame to the next so a record of what has changed must be - * kept. Changing state should be done at origin and not in the frame. State - * that is marked as changed will then be send into a stream and the changed - * status will be cleared, allowing data to only be sent/saved when actual - * changes occur. - * - * The provided SETTINGS type must support MsgPack and be copyable. An example - * of settings is camera intrinsics. - * - * COUNT is the number of settings channels available. For example, video state - * has two settings channels, one for left camera and one for right camera. - */ -template <typename SETTINGS, int COUNT> -class FrameState { - public: - typedef SETTINGS Settings; - - FrameState(); - FrameState(FrameState &); - FrameState(FrameState &&); - ~FrameState(); - - /** - * Update the pose and mark as changed. - */ - void setPose(const Eigen::Matrix4d &pose) { - pose_ = pose; - changed_ += ftl::codecs::Channel::Pose; - } - - /** - * Update the left settings and mark as changed. - */ - void setLeft(const SETTINGS &p) { - static_assert(COUNT > 0, "No settings channel"); - settings_[0] = p; - changed_ += ftl::codecs::Channel::Settings1; - } - - /** - * Update the right settings and mark as changed. - */ - void setRight(const SETTINGS &p) { - static_assert(COUNT > 1, "No second settings channel"); - settings_[1] = p; - changed_ += ftl::codecs::Channel::Settings2; - } - - /** - * Change settings using ID number. Necessary when more than 2 settings - * channels exist, otherwise use `setLeft` and `setRight`. - */ - template <int I> - void set(const SETTINGS &p) { - static_assert(I < COUNT, "Settings channel too large"); - settings_[I] = p; - changed_ += __idToChannel(I); - } - - /** - * Get the current pose. - */ - inline const Eigen::Matrix4d &getPose() const { return pose_; } - - /** - * Get the left settings. - */ - inline const SETTINGS &getLeft() const { return settings_[0]; } - - /** - * Get the right settings. - */ - inline const SETTINGS &getRight() const { return settings_[1]; } - - /** - * Get a modifiable pose reference that does not change the changed status. - * @attention Should only be used internally. - * @todo Make private eventually. - */ - inline Eigen::Matrix4d &getPose() { return pose_; } - - /** - * Get a modifiable left settings reference that does not change - * the changed status. Modifications made using this will not be propagated. - * @attention Should only be used internally. - * @todo Make private eventually. - */ - inline SETTINGS &getLeft() { return settings_[0]; } - - /** - * Get a modifiable right settings reference that does not change - * the changed status. Modifications made using this will not be propagated. - * @attention Should only be used internally. - * @todo Make private eventually. - */ - inline SETTINGS &getRight() { return settings_[1]; } - - /** - * Get a named config property. - */ - template <typename T> - std::optional<T> get(const std::string &name) { - return ftl::config::getJSON<T>(config_, name); - } - - /** - * Helper class to specialising channel based state access. - * @private - */ - template <typename T, ftl::codecs::Channel C, typename S, int N> struct As { - static const T &func(const ftl::data::FrameState<S,N> &t) { - throw FTL_Error("Type not supported for state channel"); - } - - static T &func(ftl::data::FrameState<S,N> &t) { - throw FTL_Error("Type not supported for state channel"); - } - }; - - // Specialise for pose - template <typename S, int N> - struct As<Eigen::Matrix4d,ftl::codecs::Channel::Pose,S,N> { - static const Eigen::Matrix4d &func(const ftl::data::FrameState<S,N> &t) { - return t.pose_; - } - - static Eigen::Matrix4d &func(ftl::data::FrameState<S,N> &t) { - return t.pose_; - } - }; - - // Specialise for settings 1 - template <typename S, int N> - struct As<S,ftl::codecs::Channel::Settings1,S,N> { - static const S &func(const ftl::data::FrameState<S,N> &t) { - return t.settings_[0]; - } - - static S &func(ftl::data::FrameState<S,N> &t) { - return t.settings_[0]; - } - }; - - // Specialise for settings 2 - template <typename S, int N> - struct As<S,ftl::codecs::Channel::Settings2,S,N> { - static const S &func(const ftl::data::FrameState<S,N> &t) { - return t.settings_[1]; - } - - static S &func(ftl::data::FrameState<S,N> &t) { - return t.settings_[1]; - } - }; - - // Specialise for config - template <typename S, int N> - struct As<nlohmann::json,ftl::codecs::Channel::Configuration,S,N> { - static const nlohmann::json &func(const ftl::data::FrameState<S,N> &t) { - return *t.config_; - } - - static nlohmann::json &func(ftl::data::FrameState<S,N> &t) { - return *t.config_; - } - }; - - /** - * Allow access to state items using a known channel number. By default - * these throw an exception unless specialised to accept a particular type - * for a particular channel. The specialisations are automatic for pose, - * config and SETTINGS items. - */ - template <typename T, ftl::codecs::Channel C> - T &as() { return As<T,C,SETTINGS,COUNT>::func(*this); } - - /** - * Allow access to state items using a known channel number. By default - * these throw an exception unless specialised to accept a particular type - * for a particular channel. The specialisations are automatic for pose, - * config and SETTINGS items. - */ - template <typename T, ftl::codecs::Channel C> - const T &as() const { - return As<T,C,SETTINGS,COUNT>::func(*this); - } - - /** - * Set a named config property. Also makes state as changed to be resent. - */ - template <typename T> - void set(const std::string &name, T value) { - ftl::config::setJSON<T>(config_, name, value); - changed_ += ftl::codecs::Channel::Configuration; - } - - inline const nlohmann::json &getConfig() const { return *config_; } - - inline nlohmann::json &getConfig() { return *config_; } - - /** - * Check if pose or settings have been modified and not yet forwarded. - * Once forwarded through a pipeline / stream the changed status is cleared. - */ - inline bool hasChanged(ftl::codecs::Channel c) const { return changed_.has(c); } - - /** - * Copy assignment will clear the changed status of the original. - */ - FrameState &operator=(FrameState &); - - FrameState &operator=(FrameState &&); - - /** - * Clear the changed status to unchanged. - */ - inline void clear() { changed_.clear(); } - - private: - Eigen::Matrix4d pose_; - std::array<SETTINGS,COUNT> settings_; - nlohmann::json *config_; - ftl::codecs::Channels<64> changed_; // Have the state channels changed? - - static inline ftl::codecs::Channel __idToChannel(int id) { - return (id == 0) ? ftl::codecs::Channel::Settings1 : (id == 1) ? - ftl::codecs::Channel::Settings2 : - static_cast<ftl::codecs::Channel>(static_cast<int>(ftl::codecs::Channel::Settings3)+(id-2)); - } -}; - -} -} - - -template <typename SETTINGS, int COUNT> -ftl::data::FrameState<SETTINGS,COUNT>::FrameState() : settings_({{0}}), config_(ftl::config::createJSON()) { - pose_ = Eigen::Matrix4d::Identity(); -} - -template <typename SETTINGS, int COUNT> -ftl::data::FrameState<SETTINGS,COUNT>::~FrameState() { - ftl::config::destroyJSON(config_); -} - -template <typename SETTINGS, int COUNT> -ftl::data::FrameState<SETTINGS,COUNT>::FrameState(ftl::data::FrameState<SETTINGS,COUNT> &f) { - pose_ = f.pose_; - settings_ = f.settings_; - changed_ = f.changed_; - ftl::config::copyJSON(config_, f.config_); - f.changed_.clear(); -} - -template <typename SETTINGS, int COUNT> -ftl::data::FrameState<SETTINGS,COUNT>::FrameState(ftl::data::FrameState<SETTINGS,COUNT> &&f) { - pose_ = f.pose_; - settings_ = f.settings_; - changed_ = f.changed_; - config_ = f.config_; - f.config_ = nullptr; - f.changed_.clear(); -} - -template <typename SETTINGS, int COUNT> -ftl::data::FrameState<SETTINGS,COUNT> &ftl::data::FrameState<SETTINGS,COUNT>::operator=(ftl::data::FrameState<SETTINGS,COUNT> &f) { - pose_ = f.pose_; - settings_ = f.settings_; - changed_ = f.changed_; - ftl::config::copyJSON(config_, f.config_); - f.changed_.clear(); - return *this; -} - -template <typename SETTINGS, int COUNT> -ftl::data::FrameState<SETTINGS,COUNT> &ftl::data::FrameState<SETTINGS,COUNT>::operator=(ftl::data::FrameState<SETTINGS,COUNT> &&f) { - pose_ = f.pose_; - settings_ = f.settings_; - changed_ = f.changed_; - config_ = f.config_; - f.changed_.clear(); - f.config_ = nullptr; - return *this; -} - -#endif // _FTL_DATA_FRAMESTATE_HPP_ diff --git a/components/structures/include/ftl/data/new_frame.hpp b/components/structures/include/ftl/data/new_frame.hpp index 12628cc73c757f9171eda4b967c632482b2061b0..1be1544f3caf13cd12161cda4d6c38012d4ca1f8 100644 --- a/components/structures/include/ftl/data/new_frame.hpp +++ b/components/structures/include/ftl/data/new_frame.hpp @@ -26,12 +26,29 @@ class Session; class Pool; class FrameSet; +/** + * Unique identifier for a single frame. This is stored as two 16bit numbers + * packed into a 32bit int. Every frame has a FrameID, as does every frameset. + * FrameID + Timestamp together will be a unique object within the system since + * frames cannot be duplicated. + */ struct FrameID { uint32_t id; + /** + * Frameset ID for this frame. + */ inline unsigned int frameset() const { return id >> 8; } + + /** + * Frame index within the frameset. This will correspond to the vector + * index in the frameset object. + */ inline unsigned int source() const { return id & 0xff; } + /** + * The packed int with both frameset ID and index. + */ operator uint32_t() const { return id; } /** @@ -43,21 +60,34 @@ struct FrameID { FrameID() : id(0) {} }; +/** + * A frame object can be used in 3 different scenarios. A frame mode cannot be + * changed after construction and so each mode is constructed differently. + */ enum FrameMode { - PRIMARY, - RESPONSE, - STANDALONE + PRIMARY, /// A normal frame generated by a builder + RESPONSE, /// A frame that acts as a reply to a primary frame + STANDALONE /// Not associated with a source or stream, used for storage }; +/** + * The life cycle of a frame goes through all of these frame status stages. + * From the `Pool` it is created. After the frame is populated with initial data + * it is `stored`. New data is inserted to the frame before being `flushed`. + * Finally, when the frame is destroyed the data is transfered to the `Pool` + * for memory reuse and the frame is `released`. + */ 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 + 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. + * Helper class to enable aggregation of aggregate channels. Assignment acts to + * append data to a list rather than replace that data. It allows all data + * changes to be recorded. Not thread-safe however. */ template <typename T> struct Aggregator { @@ -90,6 +120,46 @@ struct Aggregator { operator T() const { return list; } }; +/** + * A `Frame` is the primary unit of data within the system. A data source + * generates discrete blocks of data with a timestamp, these blocks are + * encapsulated in a frame that has any number of channels. A `Frame` must be + * constructed from a `Pool` object so that memory can be reused. + * + * It can be moved around but not copied since the quantity of data involved in + * a frame is huge. + * + * A frame does through the following stages: + * 1) Creation from reused memory in `Pool` + * 2) Populate with incoming initial data/changes (from stream) + * 3) Store of changes to persistent memory + * 4) Create any new data such as new video frames + * 5) Flush the data to transmit or save, becomes readonly + * 6) Release memory to `Pool` + * + * A channel stores one particular element of data of a specified type. To write + * to a channel the `create` or `set` methods must be used, this will mark the + * channel as changed but can only occur before the frame is flushed and + * readonly. A `get` method allows const access to the data as long as the + * channel exists. + * + * On change events are triggered when `store` occurs, whereas on flush events + * occur after flush. Both of these may occur on destruction if the frame was + * not stored or flushed before destruction. + * + * Some channels may fail `hasChannel` but still be marked as `available`. This + * will be due to the data not being transmitted or encoded until requested. + * + * Each frame is also associated with a `Session` object which stores all + * persistent data. Persistent data can then be accessed via any `Frame` with + * the same ID since they share a `Session`. + * + * A `Frame` provides some basic methods, however, it can be cast to other + * frame types using the cast method which provides additional wrapper + * functions. An example is `ftl::rgbd::Frame`. + * + * @see https://gitlab.utu.fi/nicolas.pope/ftl/-/wikis/Design/Frames + */ class Frame { friend class Session; friend class ftl::data::Pool; @@ -103,9 +173,26 @@ class Frame { public: + /** + * Millisecond timestamp when the frame was originally constructed and which + * was the instant the data contents were captured. + */ inline int64_t timestamp() const { return timestamp_; } + + /** + * Unique identification of data source. Combined with timestamp it will + * become a unique item of data and a singleton in the system. + */ inline FrameID id() const { return id_; } + + /** + * Access the frameset ID for this frame. + */ inline unsigned int frameset() const { return id_.frameset(); } + + /** + * Access the index of the frame within the frameset. + */ inline unsigned int source() const { return id_.source(); } public: @@ -126,16 +213,38 @@ class Frame { Frame(const Frame &)=delete; Frame &operator=(const Frame &)=delete; + /** + * Obtain the current life-cycle status of the frame. This determines what + * operations are permitted and what the behviour of the frame is. + */ inline FrameStatus status() const { return status_; } + /** + * Number of data channels in the frame. Excluding previous persistent data. + */ inline size_t size() const { return data_.size(); } + /** + * Is there data in this frame or in the persistent store for the given + * channel number? + */ bool has(ftl::codecs::Channel c) const; + /** + * Check that either this frame or the persistent store has all the + * channels in the set. + */ bool hasAll(const std::unordered_set<ftl::codecs::Channel> &cs); + /** + * @see has(Channel) + */ inline bool hasChannel(ftl::codecs::Channel c) const { return has(c); } + /** + * Does this frame have data for a given channel. This excludes any data + * that may be in the persistent store. + */ inline bool hasOwn(ftl::codecs::Channel c) const; /** @@ -145,62 +254,151 @@ class Frame { */ inline bool available(ftl::codecs::Channel c) const; + /** + * A complete set of all channels that are potentially available but may + * not currently have the data stored within this object. It means the + * source of the frame can provide the data but has not be requested to + * actually do so, or cannot due to resource constraints. + */ std::unordered_set<ftl::codecs::Channel> available() const; bool availableAll(const std::unordered_set<ftl::codecs::Channel> &cs) const; /** - * Used by a receiver to mark potential availability. + * Used by a receiver to mark potential availability. Should not be used + * elsewhere. */ inline void markAvailable(ftl::codecs::Channel c); + /** + * Has a given channel been marked as changed? + */ inline bool changed(ftl::codecs::Channel c) const; + /** + * A channel is readonly if it has been flushed. An exception is thrown if + * a write is attempted. + */ inline bool readonly(ftl::codecs::Channel c) const; + /** + * @see readonly(Channel) + */ inline bool flushed(ftl::codecs::Channel c) const; + /** + * Changes can occur from different sources for different reasons, this + * obtains the cause of the change. For example, it can be a primary local + * change or it can be received from a remote source. Change type does + * influence behaviour during store and flush actions. + */ inline ftl::data::ChangeType getChangeType(ftl::codecs::Channel c) const; + /** + * Obtain the map of all changes. + */ inline const std::unordered_map<ftl::codecs::Channel, ChangeType> &changed() const { return changed_; } + /** + * Obtains a set of all available channels. This excludes channels in the + * persistent store. + */ std::unordered_set<ftl::codecs::Channel> channels() const; + /** + * Test if the type of the channel matches the template type. Other + * functions throw exceptions if wrong type is used, but this will not. It + * will also return false if the channel is missing. + */ template <typename T> bool isType(ftl::codecs::Channel c) const; + /** + * Get a readonly const reference to the content of a channel. If the + * channel does not exist or if the template type does not match the content + * then it throws an exception. The data can either be within this frame, + * or if not in the frame then it checks the persistent store. + * + * The data may internally still be encoded and will only be decoded on the + * first call to `get`. It is therefore strongly advised that any initial + * calls to `get` are not concurrent as it will not be thread-safe. + */ template <typename T> const T &get(ftl::codecs::Channel c) const; + /** + * Should not be used directly, but allows direct access to the data for + * a channel without any attempt to cast it to type. Throws exceptions if + * the channel does not exist, but will also look in the persistent + * store. + */ const std::any &getAny(ftl::codecs::Channel c) const; + /** + * Get the hash code from the C++ `type_info` structure that corresponds to + * the current data contents. + */ inline size_t type(ftl::codecs::Channel c) const { return getAny(c).type().hash_code(); } + /** + * Should not be used. Allows modification without marking as changed. + */ std::any &getAnyMutable(ftl::codecs::Channel c); + /** + * Should not be used. Does not throw exceptions but can return a nullptr + * instead if the channel does not exist or the type does not match. + * Currently, this does not do lazy decode of data so may fail. + */ template <typename T> const T *getPtr(ftl::codecs::Channel c) const noexcept; + /** + * Should not be used. + */ template <typename T> T *getMutable(ftl::codecs::Channel c); + /** + * Mark a channel as changed even if there is no data. It can result in + * `hasChannel` giving false but `changed` giving true. Intended to be used + * internally. + */ inline void touch(ftl::codecs::Channel c) { markAvailable(c); changed_[c] = (mode_ == FrameMode::PRIMARY) ? ChangeType::PRIMARY : ChangeType::RESPONSE; } + /** + * Should not be used. + */ inline void touch(ftl::codecs::Channel c, ChangeType type) { markAvailable(c); changed_[c] = type; } + /** + * Mark the channel as unchanged. This will mean it will not be flushed, + * transmitted or saved but will still return true with `hasChannel`. + */ inline void untouch(ftl::codecs::Channel c) { changed_.erase(c); } + /** + * Create a new channel with the given template type. It will mark the + * channel as changed and return a mutable reference of the correct data + * type. It is not possible to create a channel after it has been flushed, + * this will throw an exception. The channel may have existing data from + * the memory pool which can be overwritten, but this is not true for + * every channel number (only video frames currently). + */ template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> T &create(ftl::codecs::Channel c); + /** + * Create method used for aggregate channels. @see create. + */ template <typename T, std::enable_if_t<is_list<T>::value,int> = 0> ftl::data::Aggregator<T> create(ftl::codecs::Channel c); @@ -210,7 +408,7 @@ class Frame { * `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. + * To be used by receiver, no one else. Currently unused. */ template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> T &createChange(ftl::codecs::Channel c, ftl::data::ChangeType t); @@ -218,26 +416,59 @@ class Frame { 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); + /** + * Create a change but with encoded data provided. This allows for both + * lazy decode and for subsequent data forwarding without encoding. + * + * Currently unused. + */ template <typename T> T &createChange(ftl::codecs::Channel c, ftl::data::ChangeType t, const ftl::codecs::Packet &data); + /** + * Create a channel, mark with the given change type and provided encoded + * data. Does not decode the data as it does not know the actually data + * type of this channel at this time. + * + * To be used by `receiver`. + * @see ftl::stream::Receiver + */ inline void informChange(ftl::codecs::Channel c, ftl::data::ChangeType t, const ftl::codecs::Packet &data) { createAnyChange(c, t, data); } + /** + * Create a channel, mark with a given change type but do not provide any + * data or type information. + */ inline void informChange(ftl::codecs::Channel c, ftl::data::ChangeType t) { createAnyChange(c, t); } + /** + * Create a channel, mark with a given change type and provided unencoded + * data. The data is moved into the channel. This is used by `Receiver` to + * provide a loopback functionality. + */ inline void informChange(ftl::codecs::Channel c, ftl::data::ChangeType t, std::any &data) { createAnyChange(c, t) = std::move(data); } + /** + * Retrieve all encoded data packets for a channel, if any. Note that + * encoded data is removed if the channel is modified. + */ const std::list<ftl::codecs::Packet> &getEncoded(ftl::codecs::Channel c) const; + /** Do not use. */ template <typename T, typename ...ARGS> T &emplace(ftl::codecs::Channel, ARGS...); + /** + * Can be used instead of `create` to modify channel contents. It has the + * same rules as `create`, except that if the channel does not exist then + * it will throw an exception instead of creating the channel. + */ template <typename T, std::enable_if_t<!is_list<T>::value,int> = 0> T &set(ftl::codecs::Channel c); @@ -254,10 +485,29 @@ class Frame { */ void hardRemove(ftl::codecs::Channel); + /** + * Add a callback to a channel to watch for change events. These are + * triggered by the `store` operation. Note that `Receiver` will call + * `store` on a frame before generating a frameset callback, therefore + * these events always occur and complete before the frameset is generated. + */ inline ftl::Handle onChange(ftl::codecs::Channel c, const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + /** + * Add a callback to listen for any and all changes to the frame. + * @see onChange(Channel, cb). + */ inline ftl::Handle onChange(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); + /** + * All changed channels generate a flush event when the frame is flushed + * explicitly or on destruction. There is one exception, forwarded changes + * do generate a change event but do no subsequently generate a flush event + * as they are considered completed changes. This prevents loops whilst + * ensuring everyone has a copy of the change. + * + * @see changeType + */ inline ftl::Handle onFlush(const std::function<bool(Frame&,ftl::codecs::Channel)> &cb); /** @@ -296,9 +546,19 @@ class Frame { */ void release(); - /** Send changes back through origin stream. */ + /** + * Send changes back through origin stream. Causes all channels to be + * individually flushed, resulting in flush events and each channel being + * readonly. Only changed channels are flushed. Note: A frame cannot be + * flushed multiple times and the entire frame becomes readonly after this. + */ bool flush(); + /** + * Force a flush of only a single channel, allowing the frame to continue + * to be modified (except this channel). This will generate a single + * flush event. + */ bool flush(ftl::codecs::Channel c); /** Copy persistent changes to session. To be called before dispatch. */ @@ -306,24 +566,31 @@ class Frame { /** * Should only be used by Feed class. Ignores storage rules and saves - * to session anyway. + * to session anyway. Unused. */ void forceStore(); + /** + * Iterator. + */ inline auto begin() const { return data_.begin(); } inline auto end() const { return data_.end(); } - // onBeginFlush - // onEndFlush - // onError - inline MUTEX &mutex(); /** - * Generate a new frame to respond to this one. + * Generate a new frame to respond to this one. The destruction of this + * new frame will flush the changes and results in those response changes + * being transmitted back to the original source of the frame. The original + * source will then see these changes in the next frame it attempt to + * generate. */ Frame response(); + /** + * Convert this frame to another type. That type must not have any + * additional member variables, only wrapper methods. + */ template <typename T> T &cast(); @@ -336,8 +603,17 @@ class Frame { */ static Frame make_standalone(); + /** + * The memory pool associated with this frame. Note: the pool class also + * provides `onFlush` events, allowing an event handler to respond to any + * frame that is flushed. + */ inline Pool *pool() const { return pool_; } + /** + * The persistent data store for this frame. It is also a frame object and + * can be used in the same manner. + */ inline Session *parent() const { return parent_; } inline FrameMode mode() const { return mode_; }