I have a C++ project that uses SDL, in particular SDL Events. I would like to use the event system for incoming network messages just as it is used for UI events. I can define a new event type and attach some arbitrary data (see this example). This is what I would do if I was using ordinary pointers:
Uint32 message_event_type = SDL_RegisterEvents(1);
/* In the main event loop */
while (SDL_Poll(&evt)) {
if (evt.type == message_event_type) {
Message *msg = evt.user.data1;
handle_message(msg);
}
}
/* Networking code, possibly in another thread */
Message *msg = read_message_from_network();
SDL_Event evt;
evt.type = message_event_type;
evt.user.data1 = msg;
SDL_PostEvent(evt);
Instead, I've been using shared_ptr<Message>
up to now. Messages are read-only objects once constructed, and might be used in lots of places while being handled, so I thought to use shared_ptr for them.
I would like to use a shared_ptr to the message in the network side, and also on the event handling side. If I do this:
// in networking code:
shared_ptr<Message> msg = ...
evt.user.data1 = msg.get();
// later, in event handling:
shared_ptr<Message> msg(evt.user.data1);
then there are two independent shared_ptrs and either one could delete the Message object while the either is still using it. I would need to somehow pass the shared_ptr through the SDL_UserEvent struct, which only has a couple of void *
and int fields.
Additional. Note that SDL_PostEvent
returns immediately; the event itself is put on a queue. The event may be popped from the queue by the handler well after a shared_ptr to the message has gone out of scope in the networking code. So I can't pass the address of a local shared_ptr to copy from. By the time the copying occurs, it's likely to no longer be valid.
Has anyone faced a similar problem and knows of a nice solution?
Allocate a pointer to the shared ptr with new. This calls the constructor (incrementing the reference count), but the corresponding destructor is not called so the shared_ptr will never destruct it's shared memory.
Then in the corresponding handler, just destroy the object after copying the shared_ptr bringing its reference count back to normal.
This is identical to how you'd pass any other non-primitive type through a message queue.
typedef shared_ptr<Message> MessagePtr;
Uint32 message_event_type = SDL_RegisterEvents(1);
/* In the main event loop */
while (SDL_Poll(&evt)) {
if (evt.type == message_event_type) {
// Might need to cast data1 to (shared_ptr<Message> *)
unique_ptr<MessagePtr> data (evt.user.data1);
MessagePtr msg = *data;
handle_message(msg);
}
}
/* Networking code, possibly in another thread */
MessagePtr msg = read_message_from_network();
SDL_Event evt;
evt.type = message_event_type;
evt.user.data1 = new MessagePtr (msg);
SDL_PostEvent(evt);
Messages are read-only objects once constructed
I just want to point out, this is good and even necessary for the multithreading to be safe. You might want to use shared_ptr<const Message>
Seems like ideal place to use std::enable_shared_from_this
struct Message: std::enable_shared_from_this<Message>
{
…
};
evt.user.data1 = msg.get();
// this msg uses the same refcount as msg above
shared_ptr<Message> msg = evt.user.data1.shared_from_this();
I thought of another potential technique: using placement new to store the shared_ptr
in the same space as the C struct:
SDL_Event evt;
evt.type = event_type;
// create new shared_ptr, in the same memory as evt.user.code
new (&evt.user.code) shared_ptr<Message>(msg);
SDL_PushEvent(&evt);
SDL then copies the event around as a C object, until later code extracts the message from the event:
shared_ptr<Message> get_message(SDL_Event& evt) {
// copy shared_ptr out of evt
shared_ptr<Message> msg = *reinterpret_cast<shared_ptr<Message> *>(&evt.user.code);
// destroy shared_ptr inside the event struct
(reinterpret_cast<shared_ptr<Message> *>(&evt.user.code))->~shared_ptr();
return msg;
}
There are several fields inside the event struct that should be enough space for the shared_ptr (see https://github.com/spurious/SDL-mirror/blob/master/include/SDL_events.h#L485).
I'm aware that this is a bit hacky. I'd appreciate some sanity checking of the technique.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With