I am Time, the destroyer of worlds.
So far, the events which take place in a world have all been directly in response to player input. A world really comes alive with the addition of events that are generated by the world itself. That is the subject of this chapter.
Normally code is executed as fast as the computer can process it. When the instructions are instead scheduled according to the passage of time, this is known as realtime execution. Events generated by the world are usually done in realtime, or they would all take place within a moment after starting up and would be of little interest to the players.
|
A multi-user environment such as a BYOND world cannot waste any amount of time; it must always be ready to respond to input. It is important, therefore, to realize that sleep only halts the current procedure and its callers. While one procedure is sleeping, others may still operate normally.
There are several other "sleeping" instructions in DM, each with its own purpose. (In fact you have already seen one such instruction--namely prompt. It causes the current procedure to sleep until the player finishes entering input.) They all share the behavior of causing the current procedure and its callers to halt temporarily and resume execution at a later time.
The following example uses sleep to time the motion of the heavens.
proc/Weather() for() world << "The sun rises in the east." sleep(500) world << "The noon day sun rises high in the sky." sleep(500) world << "The sun sinks low in the west." sleep(1000)
A for statement without any parameters as used here is one way of creating an infinite loop. If the body of such a loop never slept, it would cause the world to grind to a halt, ignoring further player input. (As a precaution, however, code such as an infinite loop which continues for too long is aborted to prevent the entire world from getting locked up.) By sleeping inside the loop, however, the desired effect is achieved: a periodic message about the time of day is broadcast to everyone in the world.
|
The choice of whether to use sleep or spawn is mostly a matter of convenience. In many cases either one can be used to accomplish the same thing. The structure of the code generally determines which one is more convenient. If you wish to do additional processing after the delayed code, spawn is the best choice.
The following example uses spawn to sanitize a room.
area/sickroom //how long to wait before clean cycle var/const/alarm = 100 //moment when countdown started var/start_time = 0 Enter(var/mob/entrant) if(!start_time) start_time = world.time spawn(alarm) for(entrant in src) entrant << "The disinfectant hits you. Aaahhhh!" del entrant start_time = 0 //ready for another round var/time_left = (alarm - (world.time - start_time))/10 entrant << "In [time_left] second\s this room will be disinfected." entrant << "Have a nice day!" return 1
The first time someone steps into the room, the trigger is set and the deadly code is spawned. The Enter proc continues to inform each person who comes how much time is left. When the moment comes, the dirty business is done inside the spawned block of code. Note that when the spawned code is executed, it automatically halts at the end of the block, as though it were an entirely separate procedure.
When making heavy use of realtime execution, it is helpful to know how it
all happens inside. This section describes the gears and springs that make
the world tick.
A DM world is single-threaded. That means only one
piece of code is being executed at a time. If this were not true, you would
have to worry a lot more about potential problems in your code. For
example, if a spawned block of code like the one in the previous example
deletes objects from the world, it could cause trouble if code elsewhere
were simultaneously using those objects. If the code attempted to access
variables or procedures of deleted objects, it would crash, possibly leaving
some important operation half finished.
(
If you want parallelism, however, you can still split a world across
multiple servers. If you have multiple CPUs in one machine or multiple
machines, you will have created parallel universes!
)
Since there is only a single uninterrupted thread of execution, however, you
can be much more confident when writing code. Between one statement and the
next, nothing happens. If an object exists at the end of one
statement, it will still exist at the beginning of the next.
The only exception to this rule is, of course, instructions which sleep. In
that case, you should assume that anything could have happened during the
pause. A variable that referred to an object could have become null because
the object was deleted. Before trusting it, you should check to make sure
that hasn't happened.
The src variable is handled specially in this respect. When the
src of a sleeping procedure is deleted, the procedure is canceled.
That is almost always the desired effect so it saves you the trouble of
explicitly checking for this case. When you don't want this to happen, you
can always set the src variable to null before sleeping. That makes
the procedure independent of the existence of the src object.
The server breaks time into segments called ticks. At each tick of
the server's clock, any sleeping procedures that are scheduled to happen are
called. If several procedures are waiting, they are called one after the
other in the same order they went to sleep.
Normally, the server finishes processing all the waiting procedures with
time to spare before the end of the tick. During the remaining time, it
handles any input, or if there is none it simply idles. If there are
procedures that take a long time to finish, it is possible for the server to
go into overtime. In that case the tick will take longer to finish than it
was supposed to and player input may get back-logged.
This situation is called server lag. It is similar, from the player's
point of view, to network lag, but in that case the backlog may go
both ways--either input or output may get delayed during transmission. To
tell which kind of lag you are having, you can check the world.cpu
variable. This tells you what percentage of the tick is being used up. If
it is close to 100 or above, your server is lagging.
Obviously, getting a faster computer and writing more efficient procedures are
two ways to decrease server lag. Another way is to give in and increase the
length of a tick. That can be done by changing world.tick_lag. The
longer the length of a tick, the less overhead (i.e. extra processing)
involved in running the world. Clients are also limited to sending one
command per tick, so increasing the length of the tick also helps reduce
input backlog. Of course that comes at the cost of slowing the client
down, but that is better than having the server get further and further
behind.
All timings in the game are rounded to the nearest tick. That means if you
require very quick timings, you will need to decrease world.tick_lag
to an acceptable level. By default it is 1, which means the server ticks 10
times per second. By decreasing it, you also quicken the pace at which
players can move and act. However, the cost of doing so is a greater burden
on the server and the network, either of which may cause lag.
3. Timing Specifics
3.1 Threads of Execution
3.2 Clock Ticks