Chapter 13

Realtime Events

I am Time, the destroyer of worlds.
--Bhagavat Gita

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.

1. sleep instruction

Generally speaking, realtime execution is a matter of postponing the execution of code for a certain amount of time. DM provides the sleep instruction for doing precisely that. It causes execution in the current procedure to halt for the specified amount of time.

sleep (Delay)
Delay is the amount of time to wait in 10$^th$s of seconds

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.

      world << "The sun rises in the east."
      world << "The noon day sun rises high in the sky."
      world << "The sun sinks low in the west."

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.

2. spawn statement

The spawn statement is similar to sleep except instead of delaying the entire procedure, it only postpones the following statement or block of statements. Execution simply skips around the "spawned" code and only comes back later when the specified amount of time has passed.

spawn (Delay) Statement
Delay is the postponement time in 10$^th$s of seconds.

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.

   //how long to wait before clean cycle
   var/const/alarm = 100

   //moment when countdown started
   var/start_time = 0

         start_time = world.time
            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.

3. Timing Specifics

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.

3.1 Threads of Execution

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.

3.2 Clock Ticks

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.3 Sequencing Actions

As a related note on this subject, both sleep and spawn may be used without an argument. In that case, the delay is as short as possible (i.e. just one tick). The same is true if a time is specified that is less than the length of a tick. Sometimes this feature is used when the delay is intended merely to control the order in which things happen.

For example, suppose you wanted to display a message when mobs exit a room. You might not want the mob and any others who are exiting during the same tick to see the message. The trouble is you don't know who else might be about to exit. In that case, you can simply delay the notice for an instant. That gives everyone else a chance to exit the room before the notice is displayed to the remaining occupants.

      . = ..()
      if(. && usr) spawn src << "[usr] leaves."

Notice how this example sets the return value by assigning . to the result of the parent procedure. That ensures that the exit operation is indeed allowed to proceed. Otherwise, a null return value blocks the player from leaving.

Also notice how we checked to see if usr is non-null. Otherwise, the message would be generated, not only for mobs, but for objs leaving the room too.

3.4 The Sleepless Server

A final point should be mentioned or you may run into trouble. Sleeping operations cause the current procedure, its caller, its caller's caller, and so forth to freeze for some length of time. As you have seen, you can use spawn() to avoid waiting for a sleeping operation.

As it happens, that is very similar to what the server does when it calls a procedure. Procedures called by the server include the various built-in ones like Enter, Exit, and Login. If you sleep inside one of those procedures, execution of the server will immediately resume as though you had returned from your procedure. Do not, therefore, expect the action of the server (like moving an object) to be delayed as a result.