Hi,

> [SendMessage]
> > The second way sounds better since it's 'immediate', but there are more 
risks 
> > with deadlocks.
> 
> This is an issue here, since the sound server usually responds to
> commands it receives. If the main thread is blocked because of the
> SendMessage call (is it?), we'll deadlock at this point.

It depends on what variant of SendMessage you use. I'll explain all the 
variants below, but whichever thread makes the SendMessage call is blocked 
until the message has been processed by the receiver. This is not the case if 
using SendMessageCallback, and if we will usually be giving a response on 
completion of message processing, we may wish to use this function.

> We
> could use PostMessage() for server->main communication, or queue up
> outgoing events in the server (IIRC we're already doing this) and only
> send them when process_sound() (rather than process_command()) is called,
> which we don't have to do from the message handler anyway. However, this
> could easily give us delayed responses in the range of deciseconds, which
> isn't acceptable (since the sound server thread may be sleeping for quite
> a while until it wakes up for the next sound event). This could be fixed
> if your 'sleep()' can be interrupted by some means.

Hmmm... I don't exactly follow this. But I'll ask questions below.

> > LRESULT CALLBACK
> > MainWndProc (HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
> 
> So we can effectively send 64 bits as payload (wParam and lParam), and
> have to share nMsg with some global message namespace?

Yes - two lots of 32 bits, and each message is uniquely defined in a header 
file, along the lines of:
#define UWM_MESSAGE_X  "UWM_MESSAGE_X-{580C1C32-1AE3-4013-9EC4-47D72DC508F0}"
#define UWM_MESSAGE_Y  "UWM_MESSAGE_Y-{580C1C32-1AE3-4013-9EC4-47D72DC508F0}"
etc...

> With two threads, it should be possible to do that. Let's first summarize
> the outer edges the sound server needs to touch:
> 
> Main process/thread:
>        Sound implementation must provide:
>               init()/exit()/configure()/...
>               send_message_to_server()
>               send_song()
>               poll_message()
> 
> Server process/thread:
>       Sound implementation must provide:
>               send_message_to_main()
>       Sound implementation must INVOKE:
>               snd_process_message() - each time a message is received
>               snd_process_song() - each time a song is received
>               snd_process_output() - each tick, or after a period determined
>                                       by the function's return value, etc.

So when you say 'main process' here, do you mean the main process/thread we 
currently have, with what you call 'server process' being the sound server 
thread? Is the main process what is doing the timing and sending of messages? 
If this is the main game thread, where/why would sleep() be used? Particularly, 
how is timing done - is there another thread completely whose only job is to 
send a non-queued message every second to keep things synchronised and 'wake 
up' the sound thread?

> Note that the implementation of the function calling snd_process_output()
> still needs to be determined in your case- you could be looping in that
> separate thread, too, sleeping appropriate amounts of time and relying on
> your OS' time-sharing capabilities to wake you up at an appropriate time,
> or you could instruct it to send a delayed message, if that's possible. Or
> you could loop and send a message to yourself. Or whatever.

I don't exactly follow this, but it might be of some help to us if I detail 
some of the points you raise relating to Win32 messages a little more.

This is a summary of all send message calls:

* SendMessage - immediate message delivery
  - message sender and receiver the same -> blocked until message processed
  - message sender and receiver different -> blocked until message processed

* SendMessageCallback - immediate message delivery with call to callback 
                        function when message has been processed
  - message sender and receiver the same -> not blocked / returns immediately
  - message sender and receiver different -> not blocked / returns immediately

* SendNotifyMessage - immediate message delivery
  - message sender and receiver the same -> blocked until message processed
  - message sender and receiver different -> not blocked / returns immediately

* SendMessageTimeout - too unreliable and unusable in this time-critical app

* PostMessage - queued message delivery to current thread
  - never blocked / returns immediately

* PostThreadMessage - queued message delivery to specified thread
  - never blocked / returns immediately

SendMessage and its variants strictly send non-queued messages that are sent 
immediately to the receiving thread. PostMessage strictly send queued messages 
that are put in the message queue for the receiving thread.

Once a message has been sent from a thread, if it is a SendMessage variant, 
Windows immediately calls the callback function in the receiving thread ('wakes 
it up'), and leaves the callback to handle the message by calling other 
functions or doing whatever. If it is a PostMessage variant, Windows puts the 
message in the queue for the receiving thread. The built-in message checking 
loop for the receiving thread effectively calls its own callback with the next 
message as parameter if it finds one waiting in the queue. Messages in a queue 
are processed in a FIFO order unless you override the loop and implement your 
own priorities. It should also be noted that there is a limit to the the number 
of messages that can be stored in the message queue (10,000 on Windows 2000/XP).

When using SendMessage variants, if both the message sender and receiver are 
the same thread, the callback function is still called as a subroutine, and if 
you do use something like SendMessageCallback which returns immediately, things 
could get a little unpredictable if using the same variables and memory. The 
Platform SDK docs don't really explain this. Hmmm... yuck yuck yuck. To 
summarise, if using a SendMessage variant where the message sender and receiver 
and the same thread, life is a lot harder - you either have deadlock problems 
or lose time-criticality. I think I'd use PostMessage in these cases, or else 
it's just too unpredictable.

With all this said, this may change the UNIX and Win32 approaches you have 
detailed below so they may need updating.

> UNIX approach:
> --------------
> queue_t queue;
> int pipes[2];
> 
> init()
> {
>       init(queue);
>       pipe(pipes);
>       if (!fork()) sound_server_loop();
> }
> 
> sound_server_loop()
> {
>       time_t next_time = get_now();
> 
>       while (1) {
>               calculate(time, next_time);
>               int msg_p = select(pipes, &time);
>               if (msg_p)
>                       snd_process_message(get_message_from_pipe(pipes));
> 
>               if (time < 0)
>                       next_time = snd_process_sound();
> 
>               while (!queue.empty())
>                       put_message_in_pipe(pipes, queue.pop());
>       }
> }
> 
> send_message_to_server(msg)
> {
>       put_message_in_pipe(pipes, msg);
> }
> 
> poll_message()
> {
>       while (select(pipes, 0))
>               queue.push(get_message_from_pipe(pipes));
> 
>       return queue.pop();
> }
> 
> send_message_to_main(msg)
> {
>       queue.push(msg); /* checked and sent by the main loop */
> }
> 
> 
> Win32 message approach
> ----------------------
> /* if POST_MESSAGE is defined, we'll be using this method, otherwise,
> ** we'll rely on messages interrupting 'xsleep()' in the same way
> ** signals interrupt sleep()/usleep() in UNIX  */
> 
> thread_t sound_thread;
> thread_t main_thread;
> queue_t queue_main, queue_server;
> 
> init()
> {
>       init(queue_main);
>       init(queue_server);
> 
>       main_thread = get_current_thread();
>       set_callback(main_callback);
>       sound_thread = new_thread(sound_loop);
> }
> 
> sound_loop()
> {
>       set_callback(server_callback);
>       while (1) {
>               xsleep(snd_process_sound());
> #ifndef POST_MESSAGE
>               while (!queue_server.empty())
>                       Win32_SendMessage(queue_server.pop());
> #endif
>       }
> }
> 
> main_callback(msg)
> {
>       queue_main.push(msg);
> }
> 
> server_callback(msg)
> {
>       snd_process_message(msg);
> }
> 
> send_message_to_server(msg)
> {
>       Win32_SendMessage(msg);
> }
> 
> poll_message()
> {
>       return queue_main.pop();
> }
> 
> send_message_to_main(msg)
> {
> #ifdef POST_MESSAGE
>       Win32_PostMessage(msg);
> #else
>       queue_server.push(msg);
> #endif
> }


So there we are. Can you please explain a little more with the extra info I've 
given above?

Cheers,

Alex.



Reply via email to