MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | Intermediate LPC |
| 2 | Descartes of Borg |
| 3 | November 1993 |
| 4 | |
| 5 | Chapter 6: Intermediate Inheritance |
| 6 | |
| 7 | 6.1 Basics of Inheritance |
| 8 | In the textbook LPC Basics, you learned how it is the mudlib maintains |
| 9 | consistency amoung mud objects through inheritance. Inheritance |
| 10 | allows the mud administrators to code the basic functions and such that |
| 11 | all mudlib objects, or all mudlib objects of a certain type must have so |
| 12 | that you can concentrate on creating the functions which make these |
| 13 | objects different. When you build a room, or a weapon, or a monster, |
| 14 | you are taking a set of functions already written for you and inheriting |
| 15 | them into your object. In this way, all objects on the mud can count on |
| 16 | other objects to behave in a certain manner. For instance, player objects |
| 17 | can rely on the fact that all room objects will have a function in them |
| 18 | called query_long() which describes the room. Inheritance thus keeps |
| 19 | you from having to worry about what the function query_long() should |
| 20 | look like. |
| 21 | |
| 22 | Naturally, this textbook tries to go beyond this fundamental knowledge |
| 23 | of inheritance to give the coder a better undertstanding of how |
| 24 | inheritance works in LPC programming. Without getting into detail that |
| 25 | the advanced domain coder/beginner mudlib coder simply does not yet |
| 26 | need, this chapter will try to explain exactly what happens when you |
| 27 | inherit an object. |
| 28 | |
| 29 | 6.2 Cloning and Inheritance |
| 30 | Whenever a file is referenced for the first time as an object (as opposed |
| 31 | to reading the contents of the file), the game tries to load the file into |
| 32 | memory and create an object. If the object is successfully loaded into |
| 33 | memory, it becomes as master copy. Master copies of objects may be |
| 34 | cloned but not used as actual game objects. The master copy is used to |
| 35 | support any clone objects in the game. |
| 36 | |
| 37 | The master copy is the source of one of the controversies of mud LPC |
| 38 | coding, that is whether to clone or inherit. With rooms, there is no |
| 39 | question of what you wish to do, since there should only be one instance |
| 40 | of each room object in the game. So you generally use inheritance in |
| 41 | creating rooms. Many mud administrators, including myself, however |
| 42 | encourage creators to clone the standard monster object and configure it |
| 43 | from inside room objects instead of keeping monsters in separate files |
| 44 | which inherit the standard monster object. |
| 45 | |
| 46 | As I stated above, each time a file is referenced to create an object, a |
| 47 | master copy is loaded into memory. When you do something like: |
| 48 | void reset() { |
| 49 | object ob; |
| 50 | ob = new("/std/monster"); |
| 51 | /* clone_object("/std/monster") some places */ |
| 52 | ob->set_name("foo monster"); |
| 53 | ... rest of monster config code followed by moving |
| 54 | it to the room ... |
| 55 | } |
| 56 | the driver searches to see if their is a master object called "/std/monster". |
| 57 | If not, it creates one. If it does exist, or after it has been created, the |
| 58 | driver then creates a clone object called "/std/monster#<number>". If |
| 59 | this is the first time "/std/monster" is being referenced, in effect, two |
| 60 | objects are being created: the master object and the cloned instance. |
| 61 | |
| 62 | On the other hand, let's say you did all your configuring in the create() |
| 63 | of a special monster file which inherits "/std/monster". Instead of |
| 64 | cloning the standard monster object from your room, you clone your |
| 65 | monster file. If the standard monster has not been loaded, it gets loaded |
| 66 | since your monster inherits it. In addition, a master copy of your file |
| 67 | gets loaded into memory. Finally, a clone of your monster is created |
| 68 | and moved into the room, for a total of three objects added to the game. |
| 69 | Note that you cannot make use of the master copy easily to get around |
| 70 | this. If, for example, you were to do: |
| 71 | "/wizards/descartes/my_monster"->move(this_object()); |
| 72 | instead of |
| 73 | new("/wizards/descartes/my_monster")->move(this_object()); |
| 74 | you would not be able to modify the file "my_monster.c" and update it, |
| 75 | since the update command destroys the current master version of an |
| 76 | object. On some mudlibs it also loads the new version into memory. |
| 77 | Imagine the look on a player's face when their monster disappears in |
| 78 | mid-combat cause you updated the file! |
| 79 | |
| 80 | Cloning is therefore a useful too when you plan on doing just that- |
| 81 | cloning. If you are doing nothing special to a monster which cannot be |
| 82 | done through a few call others, then you will save the mud from getting |
| 83 | loaded with useless master copies. Inheritance, however, is useful if |
| 84 | you plan to add functionality to an object (write your own functions) or |
| 85 | if you have a single configuration that gets used over and over again |
| 86 | (you have an army of orc guards all the same, so you write a special orc |
| 87 | file and clone it). |
| 88 | |
| 89 | 6.3 Inside Inheritance |
| 90 | When objects A and B inherit object C, all three objects have their own |
| 91 | set of data sharing one set of function definitions from object C. In |
| 92 | addition, A and B will have separate functions definitions which were |
| 93 | entered separately into their code. For the sake of example throughout |
| 94 | the rest of the chapter, we will use the following code. Do not be |
| 95 | disturbed if, at this point, some of the code makes no sense: |
| 96 | |
| 97 | OBJECT C |
| 98 | private string name, cap_name, short, long; |
| 99 | private int setup; |
| 100 | |
| 101 | void set_name(string str) |
| 102 | nomask string query_name(); |
| 103 | private int query_setup(); |
| 104 | static void unsetup(); |
| 105 | void set_short(string str); |
| 106 | string query_short(); |
| 107 | void set_long(string str); |
| 108 | string query_long(); |
| 109 | |
| 110 | |
| 111 | void set_name(string str) { |
| 112 | if(!query_setup()) { |
| 113 | name = str; |
| 114 | setup = 1; |
| 115 | } |
| 116 | |
| 117 | nomask string query_name() { return name; } |
| 118 | |
| 119 | private query_setup() { return setup; } |
| 120 | |
| 121 | static void unsetup() { setup = 0; } |
| 122 | |
| 123 | string query_cap_name() { |
| 124 | return (name ? capitalize(name) : ""); } |
| 125 | } |
| 126 | |
| 127 | void set_short(string str) { short = str; } |
| 128 | |
| 129 | string query_short() { return short; } |
| 130 | |
| 131 | void set_long(string str) { long = str; } |
| 132 | |
| 133 | string query_long() { return str; } |
| 134 | |
| 135 | void create() { seteuid(getuid()); } |
| 136 | |
| 137 | OBJECT B |
| 138 | inherit "/std/objectc"; |
| 139 | |
| 140 | private int wc; |
| 141 | |
| 142 | void set_wc(int wc); |
| 143 | int query_wc(); |
| 144 | int wieldweapon(string str); |
| 145 | |
| 146 | void create() { ::create(); } |
| 147 | |
| 148 | void init() { |
| 149 | if(environment(this_object()) == this_player()) |
| 150 | add_action("wieldweapon", "wield"); |
| 151 | } |
| 152 | |
| 153 | void set_wc(int x) { wc = x; } |
| 154 | |
| 155 | int query_wc() { return wc; } |
| 156 | |
| 157 | int wieldweapon(string str) { |
| 158 | ... code for wielding the weapon ... |
| 159 | } |
| 160 | |
| 161 | OBJECT A |
| 162 | inherit "/std/objectc"; |
| 163 | |
| 164 | int ghost; |
| 165 | |
| 166 | void create() { ::create(); } |
| 167 | |
| 168 | void change_name(string str) { |
| 169 | if(!((int)this_object()->is_player())) unsetup(); |
| 170 | set_name(str); |
| 171 | } |
| 172 | |
| 173 | string query_cap_name() { |
| 174 | if(ghost) return "A ghost"; |
| 175 | else return ::query_cap_name(); |
| 176 | } |
| 177 | |
| 178 | As you can see, object C is inherited both by object A and object B. |
| 179 | Object C is a representation of a much oversimplified base object, with B |
| 180 | being an equally oversimplified weapon and A being an equally |
| 181 | simplified living object. Only one copy of each function is retained in |
| 182 | memory, even though we have here three objects using the functions. |
| 183 | There are of course, three instances of the variables from Object C in |
| 184 | memory, with one instance of the variables of Object A and Object B in |
| 185 | memory. Each object thus gets its own data. |
| 186 | |
| 187 | 6.4 Function and Variable Labels |
| 188 | Notice that many of the functions above are proceeded with labels which |
| 189 | have not yet appeared in either this text or the beginner text, the labels |
| 190 | static, private, and nomask. These labels define special priveledges |
| 191 | which an object may have to its data and member functions. Functions |
| 192 | you have used up to this point have the default label public. This is |
| 193 | default to such a degree, some drivers do not support the labeling. |
| 194 | |
| 195 | A public variable is available to any object down the inheritance tree |
| 196 | from the object in which the variable is declared. Public variables in |
| 197 | object C may be accessed by both objects A and B. Similarly, public |
| 198 | functions may be called by any object down the inheritance tree from the |
| 199 | object in which they are declared. |
| 200 | |
| 201 | The opposite of public is of course private. A private variable or |
| 202 | function may only be referenced from inside the object which declares it. |
| 203 | If object A or B tried to make any reference to any of the variables in |
| 204 | object C, an error would result, since the variables are said to be out of |
| 205 | scope, or not available to inheriting classes due to their private labels. |
| 206 | Functions, however, provide a unique challenge which variables do not. |
| 207 | External objects in LPC have the ability to call functions in other objects |
| 208 | through call others. The private label does not protect against call |
| 209 | others. |
| 210 | |
| 211 | To protect against call others, functions use the label static. A function |
| 212 | which is static may only be called from inside the complete object or |
| 213 | from the game driver. By complete object, I mean object A can call |
| 214 | static functions in the object C it inherits. The static only protects against |
| 215 | external call others. In addition, this_object()->foo() is considered an |
| 216 | internal call as far as the static label goes. |
| 217 | |
| 218 | Since variables cannot be referenced externally, there is no need for an |
| 219 | equivalent label for them. Somewhere along the line, someone decided |
| 220 | to muddy up the waters and use the static label with variables to have a |
| 221 | completely separate meaning. What is even more maddening is that this |
| 222 | label has nothing to do with what it means in the C programming |
| 223 | language. A static variable is simply a variable that does not get saved to |
| 224 | file through the efun save_object() and does not get restored through |
| 225 | restore_object(). Go figure. |
| 226 | |
| 227 | In general, it is good practice to have private variables with public |
| 228 | functions, using query_*() functions to access the values of inherited |
| 229 | variables, and set_*(), add_*(), and other such functions to change |
| 230 | those values. In realm coding this is not something one really has to |
| 231 | worry a lot about. As a matter of fact, in realm coding you do not have |
| 232 | to know much of anything which is in this chapter. To be come a really |
| 233 | good realm coder, however, you have to be able to read the mudlib |
| 234 | code. And mudlib code is full of these labels. So you should work |
| 235 | around with these labels until you can read code and understand why it |
| 236 | is written that way and what it means to objects which inherit the code. |
| 237 | |
| 238 | The final label is nomask, and it deals with a property of inheritance |
| 239 | which allows you to rewrite functions which have already been defined. |
| 240 | For example, you can see above that object A rewrote the function |
| 241 | query_cap_name(). A rewrite of function is called overriding the |
| 242 | function. The most common override of a function would be in a case |
| 243 | like this, where a condition peculiar to our object (object A) needs to |
| 244 | happen on a call ot the function under certain circumstances. Putting test |
| 245 | code into object C just so object A can be a ghost is plain silly. So |
| 246 | instead, we override query_cap_name() in object A, testing to see if the |
| 247 | object is a ghost. If so, we change what happens when another object |
| 248 | queries for the cap name. If it is not a ghost, then we want the regular |
| 249 | object behaviour to happen. We therefore use the scope resolution |
| 250 | operator (::) to call the inherited version of the query_cap_name() |
| 251 | function and return its value. |
| 252 | |
| 253 | A nomask function is one which cannot be overridden either through |
| 254 | inheritance or through shadowing. Shadowing is a sort of backwards |
| 255 | inheritance which will be detailed in the advanced LPC textbook. In the |
| 256 | example above, neither object A nor object B (nor any other object for |
| 257 | that matter) can override query_name(). Since we want to use |
| 258 | query_name() as a unique identifier of objects, we don't want people |
| 259 | faking us through shadowing or inheritance. The function therefore gets |
| 260 | the nomask label. |
| 261 | |
| 262 | 6.5 Summary |
| 263 | Through inheritance, a coder may make user of functions defined in |
| 264 | other objects in order to reduce the tedium of producing masses of |
| 265 | similar objects and to increase the consistency of object behaviour across |
| 266 | mudlib objects. LPC inheritance allows objects maximum priveledges in |
| 267 | defining how their data can be accessed by external objects as well as |
| 268 | objects inheriting them. This data security is maintained through the |
| 269 | keywords, nomask, private, and static. |
| 270 | |
| 271 | In addition, a coder is able to change the functionality of non-protected |
| 272 | functions by overriding them. Even in the process of overriding a |
| 273 | function, however, an object may access the original function through |
| 274 | the scope resolution operator. |
| 275 | |
| 276 | Copyright (c) George Reese 1993 |