-
Notifications
You must be signed in to change notification settings - Fork 498
ScatteredBuffer::IFragmentsObserver #440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| { | ||
| using traits = typename T::_traits_; | ||
| using traits = typename T::_traits_; | ||
| using PayloadFragment = libcyphal::transport::PayloadFragment; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
┬─┬ノ(º_ºノ)
| using PayloadFragment = libcyphal::transport::PayloadFragment; | |
| using libcyphal::transport::PayloadFragment; |
| return bytes_to_copy; | ||
| } | ||
|
|
||
| void observeFragments(ScatteredBuffer::IFragmentsObserver& observer) const override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be cool if we had something like IInvokable<...> defined in CETL and implemented by cetl::function, to enhance composability. The user could then either use a custom implementation for this interface, or use a cetl function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internally at CETL we already have something similar, see
namespace detail
{
template <typename Result, typename... Args>
class function_handler : public rtti_helper<function_handler_typeid_t>
{
public:
virtual Result operator()(Args...) = 0;
}; // function_handler
...
but it has its issues which sooner or later we might need try to resolve. I'm talking about Args... args arguments which currently could be only copied but not moved (but I can't make it forward-able Args&&... args) - not sure yet how to address it, so it's a limitation currently).
|
|
||
| PublisherBase& operator=(PublisherBase&& other) noexcept | ||
| { | ||
| CETL_DEBUG_ASSERT(impl_ != nullptr, "Not supposed to move to already moved `this`."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such assert is wrong - "swap" usually implemented by using move constructor and then 2 move assignments, like:
_Tp __tmp = _GLIBCXX_MOVE(__a);
__a = _GLIBCXX_MOVE(__b);
__b = _GLIBCXX_MOVE(__tmp);
I found and fixed 3 such places in total.
| /// | ||
| /// @param observer The observer will be called (by `onNext` method) for each fragment of the storage. | ||
| /// | ||
| virtual void observeFragments(IFragmentsObserver& observer) const = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't actually an "observer" is it? Typically I reserve that term for things that are called back asynchronously and over time. This is more of a visitor pattern. It's basically foreach_fragment from what I can tell. If so; if this is similar to foreach, we should just use an std::function<void(PayloadFragment)> argument to make this consistent and easier to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely you mean cetl::function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thirtytwobits Scott, I'll rename it to forEachFragment, but about making it cetl::function I have concerns. Surely, it was my initial thought to make it as cetl::function, but then I had second thoughts:
cetl::functionis not very cheap. my performance measurements showed that it's relatively cheap to call it, but creation is not so:unbounded_variantbehind the scene needs to be created (with several type-erasing "handler" function pointers being initialized)cetl::rttiagain behind the scene will be used to obtain internal callable interface
- normally,
ScatteredBufferwill hold just couple of fragments; if it would be a lot of fragments then mentioned above creation overhead probably won't be noticeable, but in case of just a few fragments (1 for CAN, andN = data_size / UDP_MTUwhich is also like 1 or 2, depending ondata_sizeof cause) it feels like a waste - having
IFragmentsObserver(or if you would likeIFragmentVisitor) makes cost is just one virtual call to already existing visitor object.
What also concerns me in general is that in a lot of places of libcyphal we go extra mile for trying to achieve "zero-copy" of transferred data, or limit dynamic allocation, BUT at the same time we do a lot of copying and moving of various implicit unbounded_variants here and there, and IMO just move pressure from heap to stack. I know, heap fragmentation and etc., but still food for thoughts in my opinion.
So, what do you think about still having the interface after all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. let's so some renaming and you can keep the interface.
As for zero-copy in libcyphal; the use case we care most about is the ability to keep data in specific memory rather than requiring it to be moved into general program ram for libcyphal to use it. For example, when receiving frames that are hardware-managed buffers we want to keep the memory in these buffers until/unless the program specifically uses it to avoid the cost of moving for messages that are discarded (this allows low-cost, application-level message rate decimation). Another example is keeping all message data in a section of memory with a different cache policy than the program RAM. etc. As the project evolves we'll probably see snooping optimizations enabled which allows applications to filter messages based on an outer-data type's values without requiring full deserialization. All of this requires an architecture that ensures message memory is treated differently then general program RAM, heap, stack, or otherwise.
|



std::bad_allocatVariableLengthArray).