Extender Methods mean Polymorphism!

From Adventure Game Studio | Wiki
Jump to: navigation, search

So you think AGS doesn't support polymorphism? Well up until AGS 3.0 that would be a correct assumption. Okay we may be getting a tad bit ahead of ourselves here. Just what is this "polymorphism" anyway?

Polymorphism is a concept specifically dealing with data structures structs in AGS), inheritance, overloading, and other concepts which may seem largely foreign.

Inheritance

Inheritance is the idea that we're going to define a struct that inherits functions and properties from another type. For an example of this we can look at AGS's built-in GUI control types. Each of these types derive functions and properties from the base GUI control type.

For example if we had a struct defined such as:

 struct a {
   int ia;
 };

We could then derive a new type from our struct a by doing this:

 struct b extends a {
   int ib;
 };

Although struct b only defines the property ib it will also have the property ia which is inherited by the extends keyword. This is where properties such as Label.X and Button.Width come from; they are inherited from the GUIControl type.

Overloading

The idea of overloading is that you're going to have two (or more) functions with the same name. So how then do you know which version of the function is going to get called? Well, this can be determined by a number of factors but AGS makes it easy. In AGS you can only overload a function via an extender method.

You can't define two extenders for the same struct with the same name. However what you can do is define two extenders with the same name for two different structs. So you could implement a Fade function for both the Character and Object types. Both functions are called Fade, but AGS would know which one you're calling just by how you call it. So if you put cEgo.Fade it would know to call the Character function, and oKey.Fade would call the Object function.

Polymorphism

Now that we understand inheritance and overloading we can start to understand the idea of polymorphism. Going back to the GUIControl type we already know that several derived types (Button, Label, etc.) inherit from this type. So for any method declared on the GUIControl type (whether a built-in function or custom extender method) that method will be inherited to each of the derived types! (NOTE: As of this writing, 19 September 2009, autocomplete will not detect inherited extender methods. They can be called, but be warned they won't appear in autocomplete.)

The idea of polymorphism is that you want to let one of the derived types overload the inherited method so it will act differently.

Say for example you wanted the Label type to act differently when you call your custom extender (defined on the GUIControl type) than it does when it's called on a GUIControl object or any of the other derived types. Lucky for you AGS actually allows this behavior. All you have to do is define an extender for the Label type with the same name and it will override the inherited extender.

So we could see this in action with this crappy example!

 // header file
 import function DoSomething(this GUIControl*);
 import function DoSomething(this Label*);
 
 // script file
 function DoSomething(this GUIControl*) {
   Display("GUIControl.DoSomething called!");
   // do something with a GUIControl or derived type
 }
 
 function DoSomething(this Label*) {
   Display("Label.DoSomething called!");
   // do something with a Label
 }
 // wherever
 GUIControl *gcat = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
 gcat.DoSomething();
 btnButton.DoSomething(); // NOTE: though valid does not autocomplete
 sldSlider.DoSomething(); // NOTE: though valid does not autocomplete
 invInvWindow.DoSomething(); //  // NOTE: though valid does not autocomplete
 lblLabel.DoSomething();

We would get the output from this:

 GUIControl.DoSomething called!
 GUIControl.DoSomething called!
 GUIControl.DoSomething called!
 GUIControl.DoSomething called!
 Label.DoSomething called!

The first call is directly on a GUIControl object so it calls GUIControl.DoSomething as you'd expect. The next three are on derived types of the GUIControl type (with no overloaded extender defined) so they also call the GUIControl.DoSomething method. The final call is on a Label object which does have an overloaded extender defined, so only the overloaded extender, Label.DoSomething, gets called.

The extender on the Label type completely overrides the GUIControl type's extender method. You can overload as many derived types as you want (even overloading them all if you wish!) but for those types that aren't overloaded the base type's extender method will be called instead. This is precisely what polymorphism is, and that's how you do it in AGS (not a particularly useful example mind you, though it should get the idea across).

In order for polymorphism to be applied in AGS the overloaded functions must be declared as extender methods. The base function can be declared either as an extender method or directly as part of the struct definition. Yes, this does in fact mean you can apply polymorphism to the built-in types. However the only built-in type(s) which would directly support this (which is to say, have a base type from which they are derived) would be the derived GUI control types (Button, Label, etc.).

So you could if you wanted to provide overloaded versions of the BringToFront, SendToBack, SetPosition, or SetSize methods for any of the derived GUI control types. How useful that may be is entirely up to you.

What may in fact be more useful is just the ability to override custom functions. Again, it's in your hands, and up to your imagination.

Calling the base functions

In the world of polymorphism you may find at times that you want to apply a polymorphic overload of a function, but still call the base function as well. If you're working with the GUIControl type this is simple because a GUIControl* is allowed to point at the GUIControl object within any pointer of any of the derived types. That can be done such as:

 function DoSomething(this Label*) {
   Display("Label.DoSomething called!");
   GUIControl *gc = this;
   gc.DoSomething();
 }

As you can see calling the extender on the GUIControl object directly will call the base function. Even though the object is a Label, the function call is based on the calling object which in this case is a GUIControl.

For custom data types this is a bit more complex since you can't declare pointers to custom types. What you would have to do is have some type of conversion method. For example in the Stack module by monkey_05_06 (myself) I provide a conversion function Stack.Copy which returns a copy of the Stack object in the form of StackData (which is really just an AGS String with the data stored in a specialized format). So if I declared a type which would inherit from the Stack type I could then do this:

 struct Stack2 extends Stack {
   // some new functions and whatnot
 };
 
 bool Push(this Stack2*, StackData data) { // note the parameter list does NOT have to be the same
   // do something
   Stack s;
   s.LoadFromStack(this.Copy()); // load this object into the temporary stack from the base Copy function
   bool success = s.Push(data, 0, false); // insert at front of stack
   this.LoadFromStack(s.Copy()); // load the temporary stack object into this object via the base LoadFromStack function
   return success;
 }

So again you can see it's a bit more complicated for custom types, but still feasible.