| Intermediate LPC |
| Descartes of Borg |
| November 1993 |
| |
| Chapter 6: Intermediate Inheritance |
| |
| 6.1 Basics of Inheritance |
| In the textbook LPC Basics, you learned how it is the mudlib maintains |
| consistency amoung mud objects through inheritance. Inheritance |
| allows the mud administrators to code the basic functions and such that |
| all mudlib objects, or all mudlib objects of a certain type must have so |
| that you can concentrate on creating the functions which make these |
| objects different. When you build a room, or a weapon, or a monster, |
| you are taking a set of functions already written for you and inheriting |
| them into your object. In this way, all objects on the mud can count on |
| other objects to behave in a certain manner. For instance, player objects |
| can rely on the fact that all room objects will have a function in them |
| called query_long() which describes the room. Inheritance thus keeps |
| you from having to worry about what the function query_long() should |
| look like. |
| |
| Naturally, this textbook tries to go beyond this fundamental knowledge |
| of inheritance to give the coder a better undertstanding of how |
| inheritance works in LPC programming. Without getting into detail that |
| the advanced domain coder/beginner mudlib coder simply does not yet |
| need, this chapter will try to explain exactly what happens when you |
| inherit an object. |
| |
| 6.2 Cloning and Inheritance |
| Whenever a file is referenced for the first time as an object (as opposed |
| to reading the contents of the file), the game tries to load the file into |
| memory and create an object. If the object is successfully loaded into |
| memory, it becomes as master copy. Master copies of objects may be |
| cloned but not used as actual game objects. The master copy is used to |
| support any clone objects in the game. |
| |
| The master copy is the source of one of the controversies of mud LPC |
| coding, that is whether to clone or inherit. With rooms, there is no |
| question of what you wish to do, since there should only be one instance |
| of each room object in the game. So you generally use inheritance in |
| creating rooms. Many mud administrators, including myself, however |
| encourage creators to clone the standard monster object and configure it |
| from inside room objects instead of keeping monsters in separate files |
| which inherit the standard monster object. |
| |
| As I stated above, each time a file is referenced to create an object, a |
| master copy is loaded into memory. When you do something like: |
| void reset() { |
| object ob; |
| ob = new("/std/monster"); |
| /* clone_object("/std/monster") some places */ |
| ob->set_name("foo monster"); |
| ... rest of monster config code followed by moving |
| it to the room ... |
| } |
| the driver searches to see if their is a master object called "/std/monster". |
| If not, it creates one. If it does exist, or after it has been created, the |
| driver then creates a clone object called "/std/monster#<number>". If |
| this is the first time "/std/monster" is being referenced, in effect, two |
| objects are being created: the master object and the cloned instance. |
| |
| On the other hand, let's say you did all your configuring in the create() |
| of a special monster file which inherits "/std/monster". Instead of |
| cloning the standard monster object from your room, you clone your |
| monster file. If the standard monster has not been loaded, it gets loaded |
| since your monster inherits it. In addition, a master copy of your file |
| gets loaded into memory. Finally, a clone of your monster is created |
| and moved into the room, for a total of three objects added to the game. |
| Note that you cannot make use of the master copy easily to get around |
| this. If, for example, you were to do: |
| "/wizards/descartes/my_monster"->move(this_object()); |
| instead of |
| new("/wizards/descartes/my_monster")->move(this_object()); |
| you would not be able to modify the file "my_monster.c" and update it, |
| since the update command destroys the current master version of an |
| object. On some mudlibs it also loads the new version into memory. |
| Imagine the look on a player's face when their monster disappears in |
| mid-combat cause you updated the file! |
| |
| Cloning is therefore a useful too when you plan on doing just that- |
| cloning. If you are doing nothing special to a monster which cannot be |
| done through a few call others, then you will save the mud from getting |
| loaded with useless master copies. Inheritance, however, is useful if |
| you plan to add functionality to an object (write your own functions) or |
| if you have a single configuration that gets used over and over again |
| (you have an army of orc guards all the same, so you write a special orc |
| file and clone it). |
| |
| 6.3 Inside Inheritance |
| When objects A and B inherit object C, all three objects have their own |
| set of data sharing one set of function definitions from object C. In |
| addition, A and B will have separate functions definitions which were |
| entered separately into their code. For the sake of example throughout |
| the rest of the chapter, we will use the following code. Do not be |
| disturbed if, at this point, some of the code makes no sense: |
| |
| OBJECT C |
| private string name, cap_name, short, long; |
| private int setup; |
| |
| void set_name(string str) |
| nomask string query_name(); |
| private int query_setup(); |
| static void unsetup(); |
| void set_short(string str); |
| string query_short(); |
| void set_long(string str); |
| string query_long(); |
| |
| |
| void set_name(string str) { |
| if(!query_setup()) { |
| name = str; |
| setup = 1; |
| } |
| |
| nomask string query_name() { return name; } |
| |
| private query_setup() { return setup; } |
| |
| static void unsetup() { setup = 0; } |
| |
| string query_cap_name() { |
| return (name ? capitalize(name) : ""); } |
| } |
| |
| void set_short(string str) { short = str; } |
| |
| string query_short() { return short; } |
| |
| void set_long(string str) { long = str; } |
| |
| string query_long() { return str; } |
| |
| void create() { seteuid(getuid()); } |
| |
| OBJECT B |
| inherit "/std/objectc"; |
| |
| private int wc; |
| |
| void set_wc(int wc); |
| int query_wc(); |
| int wieldweapon(string str); |
| |
| void create() { ::create(); } |
| |
| void init() { |
| if(environment(this_object()) == this_player()) |
| add_action("wieldweapon", "wield"); |
| } |
| |
| void set_wc(int x) { wc = x; } |
| |
| int query_wc() { return wc; } |
| |
| int wieldweapon(string str) { |
| ... code for wielding the weapon ... |
| } |
| |
| OBJECT A |
| inherit "/std/objectc"; |
| |
| int ghost; |
| |
| void create() { ::create(); } |
| |
| void change_name(string str) { |
| if(!((int)this_object()->is_player())) unsetup(); |
| set_name(str); |
| } |
| |
| string query_cap_name() { |
| if(ghost) return "A ghost"; |
| else return ::query_cap_name(); |
| } |
| |
| As you can see, object C is inherited both by object A and object B. |
| Object C is a representation of a much oversimplified base object, with B |
| being an equally oversimplified weapon and A being an equally |
| simplified living object. Only one copy of each function is retained in |
| memory, even though we have here three objects using the functions. |
| There are of course, three instances of the variables from Object C in |
| memory, with one instance of the variables of Object A and Object B in |
| memory. Each object thus gets its own data. |
| |
| 6.4 Function and Variable Labels |
| Notice that many of the functions above are proceeded with labels which |
| have not yet appeared in either this text or the beginner text, the labels |
| static, private, and nomask. These labels define special priveledges |
| which an object may have to its data and member functions. Functions |
| you have used up to this point have the default label public. This is |
| default to such a degree, some drivers do not support the labeling. |
| |
| A public variable is available to any object down the inheritance tree |
| from the object in which the variable is declared. Public variables in |
| object C may be accessed by both objects A and B. Similarly, public |
| functions may be called by any object down the inheritance tree from the |
| object in which they are declared. |
| |
| The opposite of public is of course private. A private variable or |
| function may only be referenced from inside the object which declares it. |
| If object A or B tried to make any reference to any of the variables in |
| object C, an error would result, since the variables are said to be out of |
| scope, or not available to inheriting classes due to their private labels. |
| Functions, however, provide a unique challenge which variables do not. |
| External objects in LPC have the ability to call functions in other objects |
| through call others. The private label does not protect against call |
| others. |
| |
| To protect against call others, functions use the label static. A function |
| which is static may only be called from inside the complete object or |
| from the game driver. By complete object, I mean object A can call |
| static functions in the object C it inherits. The static only protects against |
| external call others. In addition, this_object()->foo() is considered an |
| internal call as far as the static label goes. |
| |
| Since variables cannot be referenced externally, there is no need for an |
| equivalent label for them. Somewhere along the line, someone decided |
| to muddy up the waters and use the static label with variables to have a |
| completely separate meaning. What is even more maddening is that this |
| label has nothing to do with what it means in the C programming |
| language. A static variable is simply a variable that does not get saved to |
| file through the efun save_object() and does not get restored through |
| restore_object(). Go figure. |
| |
| In general, it is good practice to have private variables with public |
| functions, using query_*() functions to access the values of inherited |
| variables, and set_*(), add_*(), and other such functions to change |
| those values. In realm coding this is not something one really has to |
| worry a lot about. As a matter of fact, in realm coding you do not have |
| to know much of anything which is in this chapter. To be come a really |
| good realm coder, however, you have to be able to read the mudlib |
| code. And mudlib code is full of these labels. So you should work |
| around with these labels until you can read code and understand why it |
| is written that way and what it means to objects which inherit the code. |
| |
| The final label is nomask, and it deals with a property of inheritance |
| which allows you to rewrite functions which have already been defined. |
| For example, you can see above that object A rewrote the function |
| query_cap_name(). A rewrite of function is called overriding the |
| function. The most common override of a function would be in a case |
| like this, where a condition peculiar to our object (object A) needs to |
| happen on a call ot the function under certain circumstances. Putting test |
| code into object C just so object A can be a ghost is plain silly. So |
| instead, we override query_cap_name() in object A, testing to see if the |
| object is a ghost. If so, we change what happens when another object |
| queries for the cap name. If it is not a ghost, then we want the regular |
| object behaviour to happen. We therefore use the scope resolution |
| operator (::) to call the inherited version of the query_cap_name() |
| function and return its value. |
| |
| A nomask function is one which cannot be overridden either through |
| inheritance or through shadowing. Shadowing is a sort of backwards |
| inheritance which will be detailed in the advanced LPC textbook. In the |
| example above, neither object A nor object B (nor any other object for |
| that matter) can override query_name(). Since we want to use |
| query_name() as a unique identifier of objects, we don't want people |
| faking us through shadowing or inheritance. The function therefore gets |
| the nomask label. |
| |
| 6.5 Summary |
| Through inheritance, a coder may make user of functions defined in |
| other objects in order to reduce the tedium of producing masses of |
| similar objects and to increase the consistency of object behaviour across |
| mudlib objects. LPC inheritance allows objects maximum priveledges in |
| defining how their data can be accessed by external objects as well as |
| objects inheriting them. This data security is maintained through the |
| keywords, nomask, private, and static. |
| |
| In addition, a coder is able to change the functionality of non-protected |
| functions by overriding them. Even in the process of overriding a |
| function, however, an object may access the original function through |
| the scope resolution operator. |
| |
| Copyright (c) George Reese 1993 |