> On 21 Feb 2018, at 10:45, Lukáš Hrázký <lhra...@redhat.com> wrote: > > On Tue, 2018-02-20 at 16:41 +0100, Christophe de Dinechin wrote: >>> On 20 Feb 2018, at 15:29, Lukáš Hrázký <lhra...@redhat.com> wrote: >>> >>> On Fri, 2018-02-16 at 17:15 +0100, Christophe de Dinechin wrote: >>>> From: Christophe de Dinechin <dinec...@redhat.com> >>>> >>>> - The Stream class now deals with locking and sending messages >>>> - The Message<> template class deals with the general writing mechanisms >>>> - Three classes, FormatMessage, FrameMessage and X11CursorMessage >>>> represent individual messages >>>> >>>> The various classes should be moved to separate headers in a subsequent >>>> operation >>>> >>>> The design uses templates to avoid any runtime overhead. All the calls are >>>> static. >>> >>> All in all, a nice way to encapsulate the sending of messages. What I >>> worked on, actually, was the receiving of messages, as that is actually >>> more important for separating the code (as seen later in the problem >>> you had with client_codecs and streaming_requested static variables). >>> >>> I am now wondering how to merge it with your changes and whether the >>> same Message class hierarchy could be used (without making a mess out >>> of it). We should discuss it later. >> >> Do you have your WIP stuff in a private branch I could look at? Maybe I can >> help with the rebasing. > > I'll push it somewhere so you can have a look, but I can manage the > rebase myself :) It would still be an effort to find out what you did > during the rebase. > >> I would probably keep input and output messages in separate classes. I can’t >> see much commonality between the two. Using the same CRTP for input >> messages, and maybe rename Message as OutputMessage… > > Agreed, probably... > >>> >>>> Signed-off-by: Christophe de Dinechin <dinec...@redhat.com> >>>> --- >>>> src/spice-streaming-agent.cpp | 250 >>>> ++++++++++++++++++++---------------------- >>>> 1 file changed, 117 insertions(+), 133 deletions(-) >>>> >>>> diff --git a/src/spice-streaming-agent.cpp b/src/spice-streaming-agent.cpp >>>> index a989ee7..c174ea4 100644 >>>> --- a/src/spice-streaming-agent.cpp >>>> +++ b/src/spice-streaming-agent.cpp >>>> @@ -48,24 +48,6 @@ namespace spice >>>> namespace streaming_agent >>>> { >>>> >>>> -struct FormatMessage >>>> -{ >>>> - StreamDevHeader hdr; >>>> - StreamMsgFormat msg; >>>> -}; >>>> - >>>> -struct DataMessage >>>> -{ >>>> - StreamDevHeader hdr; >>>> - StreamMsgData msg; >>>> -}; >>>> - >>>> -struct CursorMessage >>>> -{ >>>> - StreamDevHeader hdr; >>>> - StreamMsgCursorSet msg; >>>> -}; >>>> - >>>> class Stream >>>> { >>>> public: >>>> @@ -79,24 +61,131 @@ public: >>>> { >>>> close(streamfd); >>>> } >>>> - operator int() { return streamfd; } >>>> >>>> int have_something_to_read(int timeout); >>>> int read_command_from_device(void); >>>> int read_command(bool blocking); >>>> >>>> + >>>> + template <typename Message, typename ...PayloadArgs> >>>> + bool send(PayloadArgs... payload) >>>> + { >>>> + Message message(payload...); >>>> + std::lock_guard<std::mutex> stream_guard(mutex); >>>> + size_t expected = message.size(payload...); >>>> + size_t written = message.write(*this, payload...); >>>> + return written == expected; >>>> + } >>> >>> Do you purposefully avoid throwing exceptions here, returning a bool? >> >> You really love exceptions, don’t you ;-) > > Well... I don't always get errors, but when I do, I use exceptions to > handle them. :D > > Really, that's what it is. Error = exception.
No. C++CG E2 "Throw an exception to signal that a function can't perform its assigned task”. Examples of errors that are not exceptions: FP NaN and infinities, compiler errors (and more generally parsing errors), etc. A parser error is not an exception because it’s the job of the parser to detect the error… I could also give examples of exceptions that are not errors, though it’s more subtle and OT. Consider a C++ equivalent of an interrupted system call. Also, is bad_alloc an “error" or a limitation? (one could argue that the error would be e.g. to give you some memory belonging to someone else ;-) > That's what they teach > you at the uni and what we've always done at my previous job. It's the > language's designated mechanism to deal with errors, standard library > uses them, ... > > You brought new light into the topic for me, but I still don't know how > to deal with it... Though the fact that you are the author of the > exception handling implementation and you are reluctant to use the > exception really speaks volumes :) > >> I usually reserve exceptions for cases which are truly “catastrophic”, i.e. >> going the long way and unwindig is the right way to do it. Unwinding the >> stack is a very messy business, takes a lot of time, and if you can just >> return, it’s about one and a half gazillion times faster, generates smaller >> code, etc etc. >> >> In this specific case, though, I think that unwinding could be seen as >> appropriate, since ATM we have nothing better than error + abort when it >> happens. Plus this might avoid a scenario where you could write twice if the >> first write fails. So I’m sold, I think this is the right thing to do. >> >>> The error and exception could actually be checked as low as at the end >>> of the write_all() method, avoiding all the latter size returning and >>> checking? >>> >>>> + >>>> size_t write_all(const void *buf, const size_t len); >>>> - int send_format(unsigned w, unsigned h, uint8_t c); >>>> - int send_frame(const void *buf, const unsigned size); >>>> - void send_cursor(uint16_t width, uint16_t height, >>>> - uint16_t hotspot_x, uint16_t hotspot_y, >>>> - std::function<void(uint32_t *)> fill_cursor); >>>> >>>> private: >>>> int streamfd = -1; >>>> std::mutex mutex; >>>> }; >>>> >>>> + >>>> +template <typename Payload, typename Info> >>> >>> "Info" is quite an unimaginative name :) I understand it's the message >>> itself here and also find it curiously hard to come up with something >>> better :) "TheMessage" not very good, is it? Still better than "Info"? >>> Maybe a comment referencing the CRTP to help readers understand? >> >> I used ‘Info’ here because that’s the information about the message. > > I kind of understood that, but still... very vague for me… Will stay that way for the moment, unless you give me a really better name, sorry. > >> I can add the CRTP comment, could not remember the acronym when I wrote the >> class ;-) >> >>> >>>> +struct Message >>> >>> Any reason to not stick with the "class" keyword instead of "struct”? >> >> Not really, more a matter of habit, using struct when it’s really about data >> and everything is public. But there, “Message” evolved far away from POD to >> warrant using ‘class’. > > I do like to take the shortcut using struct to spare myself the 'public:' > line at the top, but I'd think it would be considered an inconsistency by > others... :) That’s not the reason. I tend to use it to mark a struct as “mostly data”, even if it has C++isms in it. Here, I initially saw the message is mostly data, i.e. I would not have minded the data members being public. But that’s no longer the case. Will change it. > >>> >>>> +{ >>>> + template <typename ...PayloadArgs> >>>> + Message(PayloadArgs... payload) >>> >>> "PayloadArgs... payload_args", we have something called "Payload" here >>> too, getting a bit confusing. >> >> But that’s the same thing. Payload is initialized with PayloadArgs, so it >> makes sense. >> >> Initially, I had “PayloadConstructorArgs”, but then I started using it >> outside of the ctor. > > I don't think it's the same, here Payload is the message class and > PayloadArgs is the content/args. So naming the objects accordingly IMO > helps clarity. The Payload is defined from the PayloadArgs, i.e. its constructor is Payload(PayloadArgs…). What is the problem with that? > >>> >>>> + : hdr(StreamDevHeader { >>>> + .protocol_version = STREAM_DEVICE_PROTOCOL, >>>> + .padding = 0, // Workaround GCC bug "sorry: not >>>> implemented" >>>> + .type = Info::type, >>>> + .size = (uint32_t) (Info::size(payload...) - sizeof(hdr)) >>>> + }), >>>> + msg(Info::make(payload...)) >>>> + { } >>> >>> I find it redundant that you pass the "payload..." (the args :)) to >>> Info::size() and Info::make() here and then also to Info::write() in >>> Stream::send(). >> >> I’m not sure “redundant” is the correct word. I could stash the information >> in Message, but then the compiler would have a much harder time inlining the >> actual expression, which is always very simple. Unfortunately, we have three >> different expressions that take different input, hence the CRTP and the >> passing of arguments. >> >> >>> In the three messages below, you also showcase three >>> distinct ways of serializing them, caused by this redundancy (I >>> understand it is partially given by the payload of the messages). >> >> It is *entirely* given by the payload, which I assume is a given, since it’s >> in the protocol. What else could come into play? > > What I mean is that the place you create the data for serialization is > only partially given by the payload. Because for example with the > FormatMessage, you place the data in the class as a member, but you > could have also created them on the stack at the beginning of the > write() method. And unless I'm missing something, you could do it this > way for all the cases, therefore unify the approach. For the > FrameMessage, you are constructing an empty msg member… I tell you that I don’t want to copy, say, 70K of frame data if I can avoid it, and you are suggesting I allocate 70K of stack instead? I think you did miss something, yes. (if that clarifies, that frame is given to me by the capture, I did not ask the capture to use some buffer I pre-allocated) > >>> >>> See comments under each class, which all relate to this (hope it's not >>> too confusing). >>> >>>> + >>>> + StreamDevHeader hdr; >>>> + Payload msg; >>>> +}; >>>> + >>>> + >>>> +struct FormatMessage : Message<StreamMsgFormat, FormatMessage> >>>> +{ >>>> + FormatMessage(unsigned w, unsigned h, uint8_t c) : Message(w, h, c) {} >>>> + static const StreamMsgType type = STREAM_TYPE_FORMAT; >>> >>> Could the type actually be part of the template? As in: >>> struct FormatMessage : Message<STREAM_TYPE_FORMAT, StreamMsgFormat, >>> FormatMessage> >> >> Sure, but I find it more consistent the way I wrote it. Also easier to read, >> because you have “type = TYPE” instead of just <TYPE>, but that’s really a >> personal preference. >> >>> >>>> + static size_t size(unsigned width, unsigned height, uint8_t codec) >>>> + { >>>> + return sizeof(FormatMessage); >>>> + } >>>> + static StreamMsgFormat make(unsigned w, unsigned h, uint8_t c) >>>> + { >>>> + return StreamMsgFormat{ .width = w, .height = h, .codec = c, >>>> .padding1 = {} }; >>>> + } >>>> + size_t write(Stream &stream, unsigned w, unsigned h, uint8_t c) >>>> + { >>>> + return stream.write_all(this, sizeof(*this)); >>>> + } >>>> +}; >>> >>> This message has static size, so you construct it in make() and then >>> write this whole object. >> >> The message body has static size, yes. (Header is fixed size in all three >> cases). >> >>> >>>> + >>>> + >>>> +struct FrameMessage : Message<StreamMsgData, FrameMessage> >>>> +{ >>>> + FrameMessage(const void *frame, size_t length) : Message(frame, >>>> length) {} >>>> + static const StreamMsgType type = STREAM_TYPE_DATA; >>>> + static size_t size(const void *frame, size_t length) >>>> + { >>>> + return sizeof(FrameMessage) + length; >>>> + } >>>> + static StreamMsgData make(const void *frame, size_t length) >>>> + { >>>> + return StreamMsgData(); >>>> + } >>>> + size_t write(Stream &stream, const void *frame, size_t length) >>>> + { >>>> + return stream.write_all(&hdr, sizeof(hdr)) + >>>> stream.write_all(frame, length); >>>> + } >>>> +}; >>> >>> Here the message is actually only the frame data and so you construct >>> an empty message in make(). In write() you just write the stream data >>> passed to it. >> >> Yes. >> >>> >>>> + >>>> +struct X11CursorMessage : Message<StreamMsgCursorSet, X11CursorMessage> >>>> +{ >>>> + X11CursorMessage(XFixesCursorImage *cursor) >>>> + : Message(cursor), >>>> + pixels(new uint32_t[pixel_size(cursor)]) >>>> + { >>>> + fill_pixels(cursor); >>>> + } >>>> + static const StreamMsgType type = STREAM_TYPE_CURSOR_SET; >>>> + static size_t pixel_size(XFixesCursorImage *cursor) >>>> + { >>>> + return cursor->width * cursor->height; >>>> + } >>>> + static size_t size(XFixesCursorImage *cursor) >>>> + { >>>> + return sizeof(X11CursorMessage) + sizeof(uint32_t) * >>>> pixel_size(cursor); >>>> + } >>>> + static StreamMsgCursorSet make(XFixesCursorImage *cursor) >>>> + { >>>> + return StreamMsgCursorSet >>>> + { >>>> + .width = cursor->width, >>>> + .height = cursor->height, >>>> + .hot_spot_x = cursor->xhot, >>>> + .hot_spot_y = cursor->yhot, >>>> + .type = SPICE_CURSOR_TYPE_ALPHA, >>>> + .padding1 = { }, >>>> + .data = { } >>>> + }; >>>> + } >>>> + size_t write(Stream &stream, XFixesCursorImage *cursor) >>>> + { >>>> + return stream.write_all(&hdr, sizeof(hdr)) + >>>> stream.write_all(pixels.get(), hdr.size); >>> >>> You are not writing the msg data here, might be the reson for the >>> missing cursor. >> >> Ah, good catch. I thought of not writing *this because of the extra pixels >> field, but then wrote the wrong class. >> >>> >>>> + } >>>> + void fill_pixels(XFixesCursorImage *cursor) >>>> + { >>>> + uint32_t *pixbuf = pixels.get(); >>>> + unsigned count = cursor->width * cursor->height; >>>> + for (unsigned i = 0; i < count; ++i) >>>> + pixbuf[i] = cursor->pixels[i]; >>>> + } >>>> +private: >>>> + std::unique_ptr<uint32_t[]> pixels; >>>> +}; >>> >>> (note in this class some methods could be private and some arguments >>> const) >> >> Yes. Good idea. >> >>> >>> Here you add a private member pixels, which you fill in constructor. In >>> make(), you create the static part of the message. In write(), you >>> write them. >>> >>> So, I ask, could you not actually construct all the data to write in >>> write(), and perhaps even remove the Message::msg member and the make() >>> method? >> >> Yes, but that would require to copy the frames data, which I considered an >> expensive-enough operation to try and avoid it. For cursor pixels, it’s less >> of an issue a) because we need to copy anyway, due to format conversion, and >> they happen much less frequently. > > I don't think you would need to copy the data? What I propose actually > doesn't mean any change for this method in particular? > >>> >>> That would make the classes a bit simpler? >> >> Simpler, yes but much less efficient in the important case, which is sending >> frames, where it would add one malloc / copy / free for each frame, which >> the present code avoids at the “mere” cost of passing the payload args >> around ;-) >> >> >> Thanks a lot. >> >>> >>> Cheers, >>> Lukas >>> >>>> + >>>> }} // namespace spice::streaming_agent >>>> >>>> >>>> @@ -201,66 +290,6 @@ size_t Stream::write_all(const void *buf, const >>>> size_t len) >>>> return written; >>>> } >>>> >>>> -int Stream::send_format(unsigned w, unsigned h, uint8_t c) >>>> -{ >>>> - const size_t msgsize = sizeof(FormatMessage); >>>> - const size_t hdrsize = sizeof(StreamDevHeader); >>>> - FormatMessage msg = { >>>> - .hdr = { >>>> - .protocol_version = STREAM_DEVICE_PROTOCOL, >>>> - .padding = 0, // Workaround GCC "not implemented" bug >>>> - .type = STREAM_TYPE_FORMAT, >>>> - .size = msgsize - hdrsize >>>> - }, >>>> - .msg = { >>>> - .width = w, >>>> - .height = h, >>>> - .codec = c, >>>> - .padding1 = { } >>>> - } >>>> - }; >>>> - syslog(LOG_DEBUG, "writing format\n"); >>>> - std::lock_guard<std::mutex> stream_guard(mutex); >>>> - if (write_all(&msg, msgsize) != msgsize) { >>>> - return EXIT_FAILURE; >>>> - } >>>> - return EXIT_SUCCESS; >>>> -} >>>> - >>>> -int Stream::send_frame(const void *buf, const unsigned size) >>>> -{ >>>> - ssize_t n; >>>> - const size_t msgsize = sizeof(FormatMessage); >>>> - DataMessage msg = { >>>> - .hdr = { >>>> - .protocol_version = STREAM_DEVICE_PROTOCOL, >>>> - .padding = 0, // Workaround GCC "not implemented" bug >>>> - .type = STREAM_TYPE_DATA, >>>> - .size = size /* includes only the body? */ >>>> - }, >>>> - .msg = {} >>>> - }; >>>> - >>>> - std::lock_guard<std::mutex> stream_guard(mutex); >>>> - n = write_all(&msg, msgsize); >>>> - syslog(LOG_DEBUG, >>>> - "wrote %ld bytes of header of data msg with frame of size %u >>>> bytes\n", >>>> - n, msg.hdr.size); >>>> - if (n != msgsize) { >>>> - syslog(LOG_WARNING, "write_all header: wrote %ld expected %lu\n", >>>> - n, msgsize); >>>> - return EXIT_FAILURE; >>>> - } >>>> - n = write_all(buf, size); >>>> - syslog(LOG_DEBUG, "wrote data msg body of size %ld\n", n); >>>> - if (n != size) { >>>> - syslog(LOG_WARNING, "write_all header: wrote %ld expected %u\n", >>>> - n, size); >>>> - return EXIT_FAILURE; >>>> - } >>>> - return EXIT_SUCCESS; >>>> -} >>>> - >>>> /* returns current time in micro-seconds */ >>>> static uint64_t get_time(void) >>>> { >>>> @@ -304,47 +333,6 @@ static void usage(const char *progname) >>>> exit(1); >>>> } >>>> >>>> -void >>>> -Stream::send_cursor(uint16_t width, uint16_t height, >>>> - uint16_t hotspot_x, uint16_t hotspot_y, >>>> - std::function<void(uint32_t *)> fill_cursor) >>>> -{ >>>> - if (width >= STREAM_MSG_CURSOR_SET_MAX_WIDTH || >>>> - height >= STREAM_MSG_CURSOR_SET_MAX_HEIGHT) >>>> - return; >>>> - >>>> - const uint32_t cursor_msgsize = >>>> - sizeof(CursorMessage) + width * height * sizeof(uint32_t); >>>> - const uint32_t hdrsize = sizeof(StreamDevHeader); >>>> - >>>> - std::unique_ptr<uint8_t[]> storage(new uint8_t[cursor_msgsize]); >>>> - >>>> - CursorMessage *cursor_msg = >>>> - new(storage.get()) CursorMessage { >>>> - .hdr = { >>>> - .protocol_version = STREAM_DEVICE_PROTOCOL, >>>> - .padding = 0, // Workaround GCC internal / not >>>> implemented compiler error >>>> - .type = STREAM_TYPE_CURSOR_SET, >>>> - .size = cursor_msgsize - hdrsize >>>> - }, >>>> - .msg = { >>>> - .width = width, >>>> - .height = height, >>>> - .hot_spot_x = hotspot_x, >>>> - .hot_spot_y = hotspot_y, >>>> - .type = SPICE_CURSOR_TYPE_ALPHA, >>>> - .padding1 = { }, >>>> - .data = { } >>>> - } >>>> - }; >>>> - >>>> - uint32_t *pixels = reinterpret_cast<uint32_t *>(cursor_msg->msg.data); >>>> - fill_cursor(pixels); >>>> - >>>> - std::lock_guard<std::mutex> stream_guard(mutex); >>>> - write_all(storage.get(), cursor_msgsize); >>>> -} >>>> - >>>> static void cursor_changes(Stream *stream, Display *display, int >>>> event_base) >>>> { >>>> unsigned long last_serial = 0; >>>> @@ -363,12 +351,8 @@ static void cursor_changes(Stream *stream, Display >>>> *display, int event_base) >>>> continue; >>>> >>>> last_serial = cursor->cursor_serial; >>>> - auto fill_cursor = [cursor](uint32_t *pixels) { >>>> - for (unsigned i = 0; i < cursor->width * cursor->height; ++i) >>>> - pixels[i] = cursor->pixels[i]; >>>> - }; >>>> - stream->send_cursor(cursor->width, cursor->height, >>>> - cursor->xhot, cursor->yhot, fill_cursor); >>>> + if (!stream->send<X11CursorMessage>(cursor)) >>>> + syslog(LOG_WARNING, "FAILED to send cursor\n"); >>>> } >>>> } >>>> >>>> @@ -422,7 +406,7 @@ do_capture(Stream &stream, const char *streamport, >>>> FILE *f_log) >>>> >>>> syslog(LOG_DEBUG, "wXh %uX%u codec=%u\n", width, height, >>>> codec); >>>> >>>> - if (stream.send_format(width, height, codec) == >>>> EXIT_FAILURE) >>>> + if (!stream.send<FormatMessage>(width, height, codec)) >>>> throw std::runtime_error("FAILED to send format >>>> message"); >>>> } >>>> if (f_log) { >>>> @@ -434,7 +418,7 @@ do_capture(Stream &stream, const char *streamport, >>>> FILE *f_log) >>>> hexdump(frame.buffer, frame.buffer_size, f_log); >>>> } >>>> } >>>> - if (stream.send_frame(frame.buffer, frame.buffer_size) == >>>> EXIT_FAILURE) { >>>> + if (!stream.send<FrameMessage>(frame.buffer, >>>> frame.buffer_size)) { >>>> syslog(LOG_ERR, "FAILED to send a frame\n"); >>>> break; >>>> } >>> >>> _______________________________________________ >>> Spice-devel mailing list >>> Spice-devel@lists.freedesktop.org >>> https://lists.freedesktop.org/mailman/listinfo/spice-devel >> >> > _______________________________________________ > Spice-devel mailing list > Spice-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/spice-devel _______________________________________________ Spice-devel mailing list Spice-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/spice-devel