MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | Intermediate LPC |
| 2 | Descartes of Borg |
| 3 | Novermber 1993 |
| 4 | |
| 5 | Chapter 2: The LPMud Driver |
| 6 | |
| 7 | 2.1 Review of Basic Driver/Mudlib Interaction |
| 8 | In the LPC Basics textbook, you learned a lot about the way the mudlib |
| 9 | works, specifically in relation to objects you code in order to build your |
| 10 | realm. Not much was discussed about the interaction between the |
| 11 | mudlib and the driver. You should know, however, that the driver |
| 12 | does the following: |
| 13 | 1) When an object is first loaded into memory, the driver will call |
| 14 | create() in native muds and reset() in compat muds. A creator |
| 15 | uses create() or reset() to give initial values to the object. |
| 16 | 2) At an interval setup by the game administrator, the driver calls the |
| 17 | function reset(). This allows the object to regenerate monsters and |
| 18 | such. Notice that in a compat mud, the same function is used to set up |
| 19 | initial values as is used to reset the room. |
| 20 | 3) Any time a living object comes in contact with an object of any sort, |
| 21 | the driver calls init() in the newly encountered object. This allows |
| 22 | newly encountered objects to give living objects commands to execute |
| 23 | through the add_action() efun, as well as perform other actions which |
| 24 | should happen whenever a living thing encounters a given object. |
| 25 | 4) The driver defines a set of functions known as efuns which are |
| 26 | available to all objects in the game. Examples of commonly used efuns |
| 27 | are: this_player(), this_object(), write(), say(), etc. |
| 28 | |
| 29 | 2.2 The Driver Cycle |
| 30 | The driver is a C program which runs the game. Its basic functions are |
| 31 | to accept connections from the outside world so people can login, |
| 32 | interpret the LPC code which defines LPC objects and how they |
| 33 | function in the game, and accept user input and call the appropriate LPC |
| 34 | functions which match the event. In its most simplest essence, it is an |
| 35 | unending loop. |
| 36 | |
| 37 | Once the game has booted up and is properly functioning (the boot up |
| 38 | process will be discussed in a future, advanced LPC textbook), the |
| 39 | driver enters a loop which does not terminate until the shutdown() efun |
| 40 | is legally called or a bug causes the driver program to crash. First off, |
| 41 | the driver handles any new incoming connections and passes control of |
| 42 | the connection to a login object. After that, the driver puts together a |
| 43 | table of commands which have been entered by users since the last cycle |
| 44 | of the driver. After the command table is assembled, all messages |
| 45 | scheduled to be sent to the connection from the last driver cycle are sent |
| 46 | out to the user. At this point, the driver goes through the table of |
| 47 | commands to be executed and executes each set of commands each |
| 48 | object has stored there. The driver ends its cycle by calling the function |
| 49 | heart_beat() in every object with a heart_beat() set and finally |
| 50 | performing all pending call outs. This chapter will not deal with the |
| 51 | handling of connections, but instead will focus on how the driver |
| 52 | handles user commands and heartbeats and call outs. |
| 53 | |
| 54 | 2.3 User Commands |
| 55 | As noted in section 1.2, the driver stores a list of commands for each |
| 56 | user to be executed each cycle. The commands list has the name of the |
| 57 | living object performing the command, the object which gave the living |
| 58 | object that command, and the function which is to be executed in order |
| 59 | to perform the command. The driver refers to the object which typed in |
| 60 | the command as the command giver. It is the command giver which |
| 61 | gets returned as this_player() in most cases. |
| 62 | |
| 63 | The driver starts at the top of the list of living objects with pending |
| 64 | commands, and successively performs each command it typed by calling |
| 65 | the function associated with the command and passing any arguments |
| 66 | the command giver gave as arguments to the function. As the driver |
| 67 | starts with the commands issued by a new living object, the command |
| 68 | giver variable is changed to be equal to the new living object, so that |
| 69 | during the sequence of functions initiated by that command, the efun |
| 70 | this_player() returns the object which issued the command. |
| 71 | |
| 72 | Let's look at the command buffer for an example player. Since the |
| 73 | execution of his last command, Bozo has typed "north" and "tell |
| 74 | descartes when is the next reboot". The command "north" is associated |
| 75 | with the function "Do_Move()" in the room Bozo is in (the command |
| 76 | "north" is automatically setup by the set_exits() efun in that room). The |
| 77 | command "tell" is not specifically listed as a command for the player, |
| 78 | however, in the player object there is a function called "cmd_hook()" |
| 79 | which is associated with the command "", which matches any possible |
| 80 | user input. |
| 81 | |
| 82 | Once the driver gets down to Bozo, the command giver variable is set to |
| 83 | the object which is Bozo. Then, seeing Bozo typed "north" and the |
| 84 | function "north" is associated with, the driver calls Bozo's_Room- |
| 85 | >Do_Move(0). An argument of 0 is passed to the function since Bozo |
| 86 | only typed the command "north" with no arguments. The room |
| 87 | naturally calls some functions it needs, all the while such that the efun |
| 88 | this_player() returns the object which is Bozo. Eventually, the room |
| 89 | object will call move_player() in Bozo, which in turn calls the |
| 90 | move_object() efun. This efun is responsible for changing an object's |
| 91 | environment. |
| 92 | |
| 93 | When the environment of an object changes, the commands available to |
| 94 | it from objects in its previous environment as well as from its previous |
| 95 | environment are removed from the object. Once that is done, the driver |
| 96 | calls the efun init() in the new environment as well as in each object in |
| 97 | the new environment. During each of these calls to init(), the object |
| 98 | Bozo is still the command giver. Thus all add_action() efuns from this |
| 99 | move will apply to Bozo. Once all those calls are done, control passes |
| 100 | back from the move_object() efun to the move_player() lfun in Bozo. |
| 101 | move_player() returns control back to Do_Move() in the old room, |
| 102 | which returns 1 to signify to the driver that the command action was |
| 103 | successful. If the Do_Move() function had returned 0 for some reason, |
| 104 | the driver would have written "What?" (or whatever your driver's |
| 105 | default bad command message is) to Bozo. |
| 106 | |
| 107 | Once the first command returns 1, the driver proceeds on to Bozo's |
| 108 | second command, following much the same structure. Note that with |
| 109 | "tell descartes when is the next reboot", the driver passes "descartes |
| 110 | when is the next reboot" to the function associated with tell. That |
| 111 | function in turn has to decide what to do with that argument. After that |
| 112 | command returns either 1 or 0, the driver then proceeds on to the next |
| 113 | living object with commands pending, and so on until all living objects |
| 114 | with pending commands have had their commands performed. |
| 115 | |
| 116 | 2.4 The Efuns set_heart_beat() and call_out() |
| 117 | Once all commands are performed for objects with commands pending, |
| 118 | the driver then proceeds to call the heart_beat() function in all objects |
| 119 | listed with the driver as having heartbeats. Whenever an object calls the |
| 120 | efun set_heart_beat() with a non-zero argument (depending on your |
| 121 | driver, what non-zero number may be important, but in most cases you |
| 122 | call it with the int 1). The efun set_heart_beat() adds the object which |
| 123 | calls set_heart_beat() to the list of objects with heartbeats. If you call it |
| 124 | with an argument of 0, then it removes the object from the list of objects |
| 125 | with heartbeats. |
| 126 | |
| 127 | The most common use for heartbeats in the mudlib is to heal players and |
| 128 | monsters and perform combat. Once the driver has finished dealing with |
| 129 | the command list, it goes through the heartbeat list calling heart_beat() in |
| 130 | each object in the list. So for a player, for example, the driver will call |
| 131 | heart_beat() in the player which will: |
| 132 | 1) age the player |
| 133 | 2) heal the player according to a heal rate |
| 134 | 3) check to see if there are any hunted, hunting, or attacking objects |
| 135 | around |
| 136 | 4) perform an attack if step 3 returns true. |
| 137 | 5) any other things which need to happen automatically roughly every |
| 138 | second |
| 139 | |
| 140 | Note that the more objects which have heartbeats, the more processing |
| 141 | which has to happen every cycle the mud is up. Objects with heartbeats |
| 142 | are thus known as the major hog of CPU time on muds. |
| 143 | |
| 144 | The call_out() efun is used to perform timed function calls which do not |
| 145 | need to happen as often as heartbeats, or which just happen once. Call |
| 146 | outs let you specify the function in an object you want called. The |
| 147 | general formula for call outs is: |
| 148 | call_out(func, time, args); |
| 149 | The third argument specifying arguments is optional. The first argument |
| 150 | is a string representing the name of the function to be called. The second |
| 151 | argument is how many seconds should pass before the function gets |
| 152 | called. |
| 153 | |
| 154 | Practically speaking, when an object calls call_out(), it is added to a list |
| 155 | of objects with pending call outs with the amount of time of the call out |
| 156 | and the name of the function to be called. Each cycle of the driver, the |
| 157 | time is counted down until it becomes time for the function to be called. |
| 158 | When the time comes, the driver removes the object from the list of |
| 159 | objects with pending call outs and performs the call to the call out |
| 160 | function, passing any special args originally specified by the call out |
| 161 | function. |
| 162 | |
| 163 | If you want a to remove a pending call before it occurs, you need to use |
| 164 | the remove_call_out() efun, passing the name of the function being |
| 165 | called out. The driver will remove the next pending call out to that |
| 166 | function. This means you may have some ambiguity if more than one |
| 167 | call out is pending for the same function. |
| 168 | |
| 169 | In order to make a call out cyclical, you must reissue the call_out() efun |
| 170 | in the function you called out, since the driver automatically removes the |
| 171 | function from the call out table when a call out is performed. Example: |
| 172 | |
| 173 | void foo() { call_out("hello", 10); } |
| 174 | |
| 175 | void hello() { call_out("hello", 10); } |
| 176 | |
| 177 | will set up hello() to be called every 10 seconds after foo() is first called. |
| 178 | There are several things to be careful about here. First, you must watch |
| 179 | to make sure you do not structure your call outs to be recursive in any |
| 180 | unintended fashion. Second, compare what a set_heart_beat() does |
| 181 | when compared directly to what call_out() does. |
| 182 | |
| 183 | set_heart_beat(): |
| 184 | a) Adds this_object() to a table listing objects with heartbeats. |
| 185 | b) The function heart_beat() in this_object() gets called every single |
| 186 | driver cycle. |
| 187 | |
| 188 | call_out(): |
| 189 | a) Adds this_object(), the name of a function in this_object(), a time |
| 190 | delay, and a set of arguments to a table listing functions with pending |
| 191 | call outs. |
| 192 | b) The function named is called only once, and that call comes after the |
| 193 | specified delay. |
| 194 | |
| 195 | As you can see, there is a much greater memory overhead associated |
| 196 | with call outs for part (a), yet that there is a much greater CPU overhead |
| 197 | associated with heartbeats as shown in part (b), assuming that the delay |
| 198 | for the call out is greater than a single driver cycle. |
| 199 | |
| 200 | Clearly, you do not want to be issuing 1 second call outs, for then you |
| 201 | get the worst of both worlds. Similarly, you do not want to be having |
| 202 | heart beats in objects that can perform the same functions with call outs |
| 203 | of a greater duration than 1 second. I personally have heard much talk |
| 204 | about at what point you should use a call out over a heartbeat. What I |
| 205 | have mostly heard is that for single calls or for cycles of a duration |
| 206 | greater than 10 seconds, it is best to use a call out. For repetitive calls of |
| 207 | durations less than 10 seconds, you are better off using heartbeats. I do |
| 208 | not know if this is true, but I do not think following this can do any |
| 209 | harm. |
| 210 | |
| 211 | 2.5 Summary |
| 212 | Basic to a more in depth understanding of LPC is and understanding of |
| 213 | the way in which the driver interacts with the mudlib. You should now |
| 214 | understand the order in which the driver performs functions, as well as a |
| 215 | more detailed knowledge of the efuns this_player(), add_action(), and |
| 216 | move_object() and the lfun init(). In addition to this building upon |
| 217 | knowledge you got from the LPC Basics textbook, this chapter has |
| 218 | introduced call outs and heartbeats and the manner in which the driver |
| 219 | handles them. You should now have a basic understanding of call outs |
| 220 | and heartbeats such that you can experiment with them in your realm |
| 221 | code. |
| 222 | |
| 223 | Copyright (c) George Reese 1993 |