Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are
spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the
password reset link.
Due to spam on this forum, all posts now need moderator approval.
Entire forum
➜ SMAUG
➜ Running the server
➜ game_loop(), select(), and dual core CPUs
|
game_loop(), select(), and dual core CPUs
|
It is now over 60 days since the last post. This thread is closed.
Refresh page
Pages: 1
2
3 4
5
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #30 on Mon 12 Feb 2007 05:48 AM (UTC) |
| Message
|
Quote:
One could probably substitute in something like usleep() today and it might have wider support.
This doesn't address the problem of artificially introducing a delay where none is needed. Take an example, where nothing has happened for a while, and someone types "help" - while you are inside your usleep command.
You have now guaranteed that the user has to wait 1/4 second to respond to his/her request (admittedly not long, but why make him/her wait?).
The only way to respond, virtually instantly, to input from players, is to have a single select, with an appropriate (small) timeout. That way, the moment they type something, you respond.
Then, after the (small) timeout elapses, you do your game pulses, to handle the case that you need to do something (like move a mob) even if no player is sending any commands.
However, naturally you check the elapsed time before doing that. In other words, if a fight pulse is 1/4 second, you call the fight pulse routine every 1/4 second, or as close to that as you can. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #31 on Mon 12 Feb 2007 05:52 AM (UTC) |
| Message
|
Quote:
My pseudo-code should actually be:
next tick time = last tick time + tick delay
instead of
next tick time = time + tick delay
Exactly. That was what I was trying to say in my wordy way.
Really I would say:
next tick time = last scheduled tick time + tick delay
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #32 on Mon 12 Feb 2007 05:54 AM (UTC) |
| Message
| Let me put it another way. If you make your game pulse time quite large (say 5 seconds), with the existing code it will now take 5 seconds to respond to all command input - obviously unacceptable.
With my proposal, the only thing that would take 5 seconds would be fight rounds, everything else would be instant.
Then you customise the tick time to give fights the pace that you want. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| Jon Lambert
USA (26 posts) Bio
|
| Date
| Reply #33 on Mon 12 Feb 2007 06:07 AM (UTC) |
| Message
| <I>Let me put it another way. If you make your game pulse time quite large (say 5 seconds), with the existing code it will now take 5 seconds to respond to all command input - obviously unacceptable.</I>
Yes but it isn't 5 seconds, it's the difference between 0.25 second and the time it takes to handle all the i/o. Look at the code "between" the selects. :-)
In any case the solution I used for LPMud...
int time_to_call_heart_beat;
void alarm_run (void * ignored)
{
while (1) {
Sleep(2000);
time_to_call_heart_beat = 1;
}
}
game_loop() {
blah blah blah
select i/o
...bottom of i/o loop...
if (time_to_call_heart_beat)
call_heart_beat ();
}
void call_heart_beat (void)
{
static unsigned long alarm_thread = 0;
...
do stuff for heartbeat
...
time_to_call_heart_beat = 0;
if (!alarm_thread) {
alarm_thread = _beginthread(alarm_run, 256, 0);
}
}
This is also portable to unix pthreads with a handful of macros.
| | Top |
|
| Posted by
| Jon Lambert
USA (26 posts) Bio
|
| Date
| Reply #34 on Mon 12 Feb 2007 06:13 AM (UTC) |
| Message
| > The whole point is that we really do not want to just sleep.
Oh yes you really do want to do nothing.
| | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #35 on Mon 12 Feb 2007 06:16 AM (UTC) |
| Message
|
Quote: Oh yes you really do want to do nothing. What? No we don't -- why should we? Why only process player input once per tick? Please explain your position a bit more, I'd be curious to hear your justification.
Making this single change on my MUD -- handling IO as many times as possible between ticks, while avoiding tick creep -- made the entire experience much more fluid. Players noticed it, so it's not just some academic question of what is "better design" for performance and what isn't. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #36 on Mon 12 Feb 2007 06:18 AM (UTC) |
| Message
| | By the way, the code you posted is essentially the same as what Nick and I are advocating. You are most certainly not doing nothing between ticks. I don't understand why you told me that just sleeping is what we want to do when it is not what you are doing. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Jon Lambert
USA (26 posts) Bio
|
| Date
| Reply #37 on Mon 12 Feb 2007 06:30 AM (UTC) |
| Message
| > What? No we don't -- why should we? Why only process player input once per tick? Please explain your position a bit more, I'd be curious to hear your justification.
If you read the code, you'll see the inverse is actually true. Diku's will not process the player input any faster than one command per 1/4 second. It would actually break the game as designed to do it faster.
| | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #38 on Mon 12 Feb 2007 06:32 AM (UTC) |
| Message
| | There is a difference between servicing the sockets once per tick and processing one command per tick. The code we are talking about services the sockets once per tick and then just sits there. That is what we are trying to avoid. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Jon Lambert
USA (26 posts) Bio
|
| Date
| Reply #39 on Mon 12 Feb 2007 07:53 AM (UTC) |
| Message
| > There is a difference between servicing the sockets once per tick and processing one command per tick. The code we are talking about services the sockets once per tick and then just sits there. That is what we are trying to avoid.
Again you need to read the code between the selects in Diku. There is no separation of between "servicing sockets" and processing commands in Diku. You are trying to solve a problem that just doesn't exist. It makes no sense to service socket i/o when you don't need the input.
Your design may even peg your CPU servicing a spammers I/O if they stream garbage to a socket. The original code won't. The change would require a rewrite of everything between the selects, separating out what is strictly network I/O from command and update processing. For what purpose? It's not fixing anything, because nothing is broken.
| | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #40 on Mon 12 Feb 2007 08:30 AM (UTC) |
| Message
| I think you do not understand the problem we are trying to solve, or perhaps you don't think it is a problem for you. You also seem to have forgotten about servicing sockets for output.
The loop we are talking about processes the input for EVERYBODY once per tick. Not just one person's input, but EVERYBODY's. Therefore, it's not just that each client gets one command per tick -- EVERYBODY gets one window per tick to get their packets in and out.
So, if I send a line in, and it wakes up select, and you send something in just after me, you have to wait a tick to get that command processed.
Furthermore, there is the case where (for some reason -- probably for a reason relevant before due to bandwidth concerns, but no longer) SMAUG breaks up >4k into 512b segments. Only processing sockets once per tick means that you only send 512b per tick, or 1k per second. That can create a noticeable slowdown for clients.
A client streaming garbage isn't much of a problem, because the code already has guards against too much data coming in too quickly, and just boots the player.
I do not agree that the change requires such a drastic rewrite. The notion of wait_state already slows down players' input when they do something that should prevent them from doing something else. But, a command that creates no lag, such as typing "help", has absolutely no reason to prevent the player from making another such command.
There already is a separation of out-of-game and in-game commands, using wait_states, so a massive rewrite is not necessary.
It appears to me that we simply disagree that this is a problem. You claim that only one command should be processed per tick, and we claim that is not correct behavior.
It would be helpful if you could explain positions a little more instead of just stating them. It makes it very hard to hold this discussion with you to have to keep dragging information bit by bit out of you. :) |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #41 on Mon 12 Feb 2007 07:32 PM (UTC) |
| Message
|
Quote:
There is no separation of between "servicing sockets" and processing commands in Diku. You are trying to solve a problem that just doesn't exist. It makes no sense to service socket i/o when you don't need the input.
OK, I think I see your point here. You are saying that you are happy to have, effectively:
usleep (250); // adjusted to compensate for IO time
... in your main loop, because you are using it to "pace" player input, keep fights moving at a normal pace, reduce spam, and so on.
Quote:
Diku's will not process the player input any faster than one command per 1/4 second. It would actually break the game as designed to do it faster.
Whilst I understand the argument, I think it relies on a compromise, that effectively stops the game designers changing (say) fight round times, or decreasing spam input.
- If you lower the time interval (say to 1/10 of a second), it would be more responsive to player input, but fights would go too fast.
- If you raise the time interval (say to 1/2 of a second), it would reduce spam, fights would go slower, and players would complain of lag.
Effectively you are tying in a number of different things:
- Response time to player input
- The rate at which player output is chunked (eg. big help screens)
- Fight round rate
- Other periodic events, like mob movement
- Player spam prevention
All these are being squeezed into a single, compromise, sleep time in your main loop.
A different design could use a single "select" statement to handle input/output as responsively as possible, but then various timer queues to manage the other issues. You might choose to do, for example:
- Max of 2 player commands per second
- Do a fight round 3 times a second
- Move mobs once a second
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #42 on Mon 12 Feb 2007 07:51 PM (UTC) |
| Message
|
Quote: A different design could use a single "select" statement to handle input/output as responsively as possible, but then various timer queues to manage the other issues. That is more or less what already happens. The update loop has an inner check that sees if it's time to run various sub-updates, such as the fight round update, mobile AI, area resets, and so forth.
In fact, every character already implements a form of queue for its commands, with the wait state mechanism. What that does it that it won't remove a line from the input buffer as long as the wait state is greater than zero. Therefore, if you want to introduce a (perhaps artificial) delay into input processing, you simply increase a character's wait state.
So, basically, all of this timing stuff is already implemented, even though it's not perfect (wait states, for example, allow the player to queue up commands, but not cancel that queue).
Of course, it would be nicer if this were done properly, with actual time-based queues, threads and so forth; Nick's event manager code solves the problem of a time-based event queue. Nonetheless, changing the select loop in the way we propose does not appear to require such a big rewrite at all. It seems to only entail reworking how/where the select call is made. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Jon Lambert
USA (26 posts) Bio
|
| Date
| Reply #43 on Tue 13 Feb 2007 03:19 AM (UTC) Amended on Tue 13 Feb 2007 03:22 AM (UTC) by Jon Lambert
|
| Message
| > I think you do not understand the problem we are trying to solve, or perhaps you don't think it is a problem for you. You also seem to have forgotten about servicing sockets for output.
There is no problem. At least nothing discussed here relates in any way whatsoever to Samson's timing problem on his MP kernel at all. Diku and all it's derivatives network code ought to work perfectly fine.
> The loop we are talking about processes the input for EVERYBODY once per tick. Not just one person's input, but EVERYBODY's. Therefore, it's not just that each client gets one command per tick -- EVERYBODY gets one window per tick to get their packets in and out.
> So, if I send a line in, and it wakes up select, and you send something in just after me, you have to wait a tick to get that command processed.
This indicates a fundamental understanding of multiplexed I/O or perhaps the select calls in the original code. When the first select polls on the 0 timeout value all the sockets that have I/O ready on them will be set. Nothing wakes up this select as it returns immediately. While the second select is sleeping and really while any other code in the process is running, network I/O is in fact still occuring during that time. Nothing wakes up that select either since the test sets are null. Like I said the second select() is functionally equivalent to usleep().
OTOH, the single select design with a timeout parameter will in fact exhibit the behavior you describe where only a few sockets will be caught everytime you race through the loop. And of course for the handful of ready I/O you are processing you are going to loop through that descriptor list far more times than you would with the Diku design. For example, if you have 10 people online and they all send data within the same 1/4 second, your select loop will at the worst possible case run 10 times. And that means you'll sequentially search the descriptor link list 10 times in the worst possible case. For what purpose do you burn up the CPU, when you could just process all the I/O that has occurred during the last 1/4 second all at once? And frankly that's all that matters to Diku.
> Furthermore, there is the case where (for some reason -- probably for a reason relevant before due to bandwidth concerns, but no longer) SMAUG breaks up >4k into 512b segments. Only processing sockets once per tick means that you only send 512b per tick, or 1k per second. That can create a noticeable slowdown for clients.
Errmmm from the code I've read stock Merc, ROM, Smaug and AFKMud will all send up to 4K per pulse. That would be 16K per second. That's also by design. I don't know any humans that can handle 4 screens of text per second, regardless of whether it slows their client down.
> I do not agree that the change requires such a drastic rewrite. The notion of wait_state already slows down players' input when they do something that should prevent them from doing something else. But, a command that creates no lag, such as typing "help", has absolutely no reason to prevent the player from making another such command.
Given a different game design. I don't think you understand the game affects of allowing players to issue more than one command per pulse. Just one example would be the ability to move past mobiles. Another would be changing the game to strongly favor fast typers and bots. It would be impossible to chase down a speedwalker/robot in a pk game. And if you don't think someone spamming "chat hulabulloo" or "help foo" 100 times a second causes a problem... well I think you do and this is why you would be rewriting many of the routines. See read_from_descriptor()... it breaks the input into a command. So you have to modify the command handler.
> There already is a separation of out-of-game and in-game commands, using wait_states, so a massive rewrite is not necessary.
Not really. I would not define cast "some spell", kick, save as in-game commands because just they have wait states and wear, north, wield, score as out-of-game commands because they don't. Other servers like MOO and Cold have a strong separation, but not Diku. The wait_state is used to delay the processing of commands beyond the pulse rate. So given that you'd modify the command interpretor to add a WAIT_STATE to all the commands. Simple, but the changes are adding up.
> It appears to me that we simply disagree that this is a problem. You claim that only one command should be processed per tick, and we claim that is not correct behavior.
That's the way the Diku game was designed. If you want to see all the neat side effects of that seemingly innocuous decision of your psuedo code simply change it.
> It would be helpful if you could explain positions a little more instead of just stating them. It makes it very hard to hold this discussion with you to have to keep dragging information bit by bit out of you. :)
Given that problem reported has nothing whatsoever to do with the network code present in DikuMuds, I can only wonder why it's necessary to suddenly discuss Diku networking code.
The real design problem of DikuMuds and some others is the tight coupling of the network code to the game itself. It is certainly NOT the two selects()! A network design which used two selects() as Diku does or select()/usleep() are no way better or worse than a design that uses a single select().
| | Top |
|
| Posted by
| Jon Lambert
USA (26 posts) Bio
|
| Date
| Reply #44 on Tue 13 Feb 2007 03:29 AM (UTC) |
| Message
| Nick's summary.
Yes that covers many of the problems. The problem with Diku code is the tight coupling of the networking code and the game itself. That's not unique to Diku. There is nothing wrong with how it uses select(). As a matter of fact all my servers use single select statements, but the choice to use two can be quite easily defended, despite it not being my code.
| | Top |
|
The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).
To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.
198,736 views.
This is page 3, subject is 5 pages long:
1
2
3 4
5
It is now over 60 days since the last post. This thread is closed.
Refresh page
top