Extender Question

Started by Ryan Timothy B, Sat 13/11/2010 06:24:56

Previous topic - Next topic

Ryan Timothy B

So I was Really playing around with extenders today. I've used them tons of times with the obvious AGS built in types like Character, Object, Button, GUI, etc etc.
But I've never used them on a regular variable or even a custom struct until today (not including the import method inside the struct).

Anyway, my question is this, what is the difference between this:

Code: ags

  struct sMyStruct {
    int i;
    import function test();
  };
  sMyStruct myStruct;


  function sMyStruct::test()
  {
    //etc...
  }


And this (this is the method I stumbled across today, by playing around):

Code: ags

  struct sMyStruct {
    int i;
  };
  sMyStruct myStruct;


  function test(this sMyStruct*)
  {
    //etc...
  }


They both seem to work in the same fashion, with  myStruct.test()  only the top one is more organized by showing you within the struct what you have for that struct.

Could something be done differently between the two?



Also I was wondering, I'm pretty sure it's not possible in AGS without a big workaround, is there an easy way to make an Extender method similar to how the characters and such work in AGS?

Like this how  character[0]  could be the same as  cThePlayerCharacter  or  player.
cThePlayerCharacter  ==  player  ==  character[0]

monkey0506

There are some important differences between normal methods and extender methods, primarily relating to access of the functions. Extender methods cannot have the static or protected access modifiers applied to them, so that could play a role in how you design your structs.

For example if your struct is primarily used just to group similar functions together then you might not need an instance of it, and could just define the functions as static:

Code: ags
struct MyStruct {
  import static function DoSomething();
  import static function DoSomethingElse();
  // ...
};

MyStruct.DoSomething();


The protected modifier could be used if you are using instances of the struct and have data that you don't want the end-user to access. In terms of a function you would be defining a function that could only be accessed from within other instance-specific (non-static) member functions:

Code: ags
struct MyStruct {
  protected import function SetData();
  import function DoSomething();
};

protected function MyStruct::SetData() {
  // do some stuff here
}

function MyStruct::DoSomething() {
  if (condition) this.SetData();
  // ...
}


In this example if you tried calling the SetData method outside of a member function of the MyStruct structure by using the this keyword, you would crash the game in an error.

Extender methods are very useful for various purposes. One, for example, would be that if you don't want the users to have to worry about skimming past protected data that they shouldn't have access to anyway, you could simply use an extender method in place of a protected method.

This actually does make the method more accessible though because once defined the end-user could then potentially declare an import and access the function. However, seeing as they could also just create an extender method to call the protected functions anyway, it's realistically a mute point.

So as it presently stands the only real difference is that with the normal import route you can have static methods whereas with extenders you can't; and the fact that, as you said, the first method presents a more concise representation of what exists within the struct.

It's also actually interesting to note that extender methods do have some other oddities to them. If you define an import for an extender method then that actually declares it as part of the struct and you can use normal struct scope resolution in defining the struct:

Code: ags
struct MyStruct { };

import function DoSomething(this MyStruct*);

function MyStruct::DoSomething() {
  // ...
}


Also, extender methods make simple polymorphism possible. The only built-in types that have base types defined are the GUIControl types, and with no way to cast between custom types, it's difficult to call base functions without some hackery, but you can, for example, override the BringToFront and SendToBack methods for any of the derived GUIControl types:

Code: ags
void BringToFront(this Label*) {
  GUIControl *control = this; // cast to base type
  control.BringToFront(); // call base function
  // ...do something else?
}


And the most intriguing part about that is of course that it actually works (i.e., the function is actually then called).

Polymorphism isn't formally supported though, so it should probably be treated as a quirk and not something you should ever depend on (at least not yet). Just wanted to point out some things about how much extender methods really shook things up. :)

Oh, and I don't understand your final question..if you're working with one of the built-in types it would be simple to define a synonym for a keyword, i.e.:

Code: ags
Character *myPlayer;

function game_start() {
  myPlayer = player;
}


Then you could just use "myPlayer" in place of "player". I think you were probably asking more along the lines of custom struct types though, in which case the answer would be no. With no pointers to custom structs and no way to provide any type of copy constructors..you can't really create on-the-fly synonyms for instances of custom structs..though you could use a #define to create one I suppose.

Ryan Timothy B

Thanks for the descriptive answers Monkey.

Anyway, about the extender method (if that's what it was called), like how you can call a character by the struct array number (character[ i ]) or by its scripting name cBob. There IS a work around if one wanted to make it similar to how AGS does it, but it involves a tiny bit of scripting each time you want to add a new element. And you couldn't access the variables from the extender method, so it almost makes doing it pointless.

So basically I'll just stick with the Enum method.

Code: ags

theCharacter[eCharBobby].DoWhatever();

Calin Leafshade

I'm confused? What exactly do you want to do? and why would you do it?

Ryan Timothy B

#4
Monkey has already answered it by saying he knows it's not possible.

I'm basically just experimenting. The Why isn't important.
But if you're seriously confused with what I was asking, I want to have a name pointer to an array struct. Just like how AGS has an extender (or whatever it is called) that points to what character[] of that struct. Where you can use cCharacter in place of character[5]. Same with buttons, GUI's, objects etc etc.

I have a hard time explaining it since I don't know what it's called. But Monkey knows what I'm talking about. :P

It just makes working with the code a lot more readable instead of  theArray[10]  if I wanted to access that particular array element alone.
But obviously I'll just do the enum method of  theArray[eTheParticularElement].

monkey0506

He basically is trying to create a script alias (essentially a synonym) to link to a specific item within an array. I'm not sure, but I believe that even in AGS you could do:

Code: ags
// top of script
MyStruct Items[4]

#define ItemZero Items[0]
#define ItemOne Items[1]
#define ItemTwo Items[2]
#define ItemThree Items[3]

// ..wherever..
ItemOne.DoSomething();

// at the end of the script (or last script referencing the items)
#undef ItemZero
#undef ItemOne
#undef ItemTwo
#undef ItemThree


Also, if you are using the array across multiple scripts, please be sure to be importing/exporting it properly not defining a new array for every script:

Code: ags
// Script.ash (header)

// struct definition..

import MyStruct Items[4];

// Script.asc (main script file)
MyStruct Items[4];
export Items;

Ryan Timothy B

Hmm, yeah it works, it just doesn't work with the auto complete.
Anyway, it's alright. I was mainly just hoping.

monkey0506

#define macros should work with autocomplete. Otherwise things like AGS_MAX_HOTSPOTS wouldn't show up. Is it possible that the autocomplete just didn't refresh its cache?

Khris

I think Ryan means when he types "ItemZero" and then the dot, there's no window showing the struct members.

Ryan Timothy B


monkey0506

#10
Ah well, if you're not concerned about allocating double the amount of memory, you could do like I did in the Stack module* and just trick/hack autocomplete:

Code: ags
struct MyStruct {
  import function DoSomething();
};

MyStruct ItemZero;
MyStruct ItemOne;
MyStruct ItemTwo;
MyStruct ItemThree;
MyStruct Items[4];
#define ItemZero Items[0] // $AUTOCOMPLETEIGNORE$
#define ItemOne Items[1] // $AUTOCOMPLETEIGNORE$
#define ItemTwo Items[2] // $AUTOCOMPLETEIGNORE$
#define ItemThree Items[3] // $AUTOCOMPLETEIGNORE$


I promise that you'll get autocomplete on your items that way, though like I said you'll end up allocating twice as much memory. ::)

Older versions of AGS would have allowed you to do this without double-allocation of memory by simply putting the aliases in a comment, but I believe that autocomplete is smarter now than it used to be..IIRC.

*I didn't double-allocate memory in the module, but I just used the same type of thing to build a custom autocomplete cache for a virtual type (essentially a typedef).

Ryan Timothy B

Yeah storing double the memory was the only approach I could think of as well. Meh.
I'll stick with the enum method definitely. :P

Thanks for answering the questions on the two exact extender methods and teaching me with your last post about $AUTOCOMPLETEIGNORE$. I didn't know AGS had any comment checking like that.

And with that, I decided to check if the triple slashes work before a function to work as the summary just like in C#. And it does! Bloody hell.


In case anyone else doesn't know, this will give you a summary:
Code: ags

///This function does nothing
function Nothing()
{
  //..etc
}



Are there any other cool tricks like that hiding away?

monkey0506

#12
If there is a line with three or more forward slashes (///) before a function declaration (this can be the function import or function definition..depending whether an import exists) then it will appear as a calltip summary. This even takes place if there is whitespace and/or empty lines between the triple-(or more)-slashed comment..which could potentially have undesired results as I found out.. ::)

There are a few autocomplete tags that I am aware of:

Quote$AUTOCOMPLETEIGNORE$ - Autocomplete will ignore this line, meaning it will not cache any variables, function imports, etc. that appear on this line

$AUTOCOMPLETESTATICONLY$ - If you're using static functions within a struct, use this to make them not appear on instances of the struct

$AUTOCOMPLETENOINHERIT$ - If you are using the inheritance available to AGS structs, you can use this to prevent the member/function from appearing for instances of derived types

Well that's actually only three..but those are the only ones I'm certain of off the top of my head that exist.

Edit:

Actually, I just thought of a really..really quirky way to avoid the double-allocation issue whilst still providing script aliases with autocomplete.

Code: ags
managed struct MyStructBase {
  import function DoSomething();
};

struct MyStruct extends MyStructBase { };

MyStructBase *ItemZero;
MyStructBase *ItemOne;
MyStructBase *ItemTwo;
MyStructBase *ItemThree;
MyStruct Items[4];
#define ItemZero Items[0] // $AUTOCOMPLETEIGNORE$
#define ItemOne Items[1] // $AUTOCOMPLETEIGNORE$
#define ItemTwo Items[2] // $AUTOCOMPLETEIGNORE$
#define ItemThree Items[3] // $AUTOCOMPLETEIGNORE$


Or, if you were concerned about a false managed type showing up in autocomplete..

Code: ags
managed struct MyStruct {
  import function DoSomething();
};

struct __MyStruct extends MyStruct { }; // $AUTOCOMPLETEIGNORE$ --may not actually be able to ignore the struct definition

MyStruct *ItemZero;
MyStruct *ItemOne;
MyStruct *ItemTwo;
MyStruct *ItemThree;

#define MyStruct __MyStruct // $AUTOCOMPLETEIGNORE$

MyStruct Items[4];
#define ItemZero Items[0] // $AUTOCOMPLETEIGNORE$
#define ItemOne Items[1] // $AUTOCOMPLETEIGNORE$
#define ItemTwo Items[2] // $AUTOCOMPLETEIGNORE$
#define ItemThree Items[3] // $AUTOCOMPLETEIGNORE$


Oh, and pointer-types allocate 4 bytes of memory, but I'm not sure if that applies to null pointers..so this route could potentially create a memory increase equivalent to adding an additional int to the structure.

monkey0506

I realize I'm double posting but the last post was sorta full..:=..and this is unrelated to what's therein.

I thought of another "hack" involving comments, something that's come in use to me..what I refer to as "comment block switching".

This doesn't work with most compilers, but that's just why AGS is so much better. The idea is that you're going to use two different comment blocks with a switch between them to control one of them being commented out and the other not being commented. The switch is like..half of a comment block. To clarify what I mean:

Code: ags
/*

This is a block comment, or what I am referring to as a "comment block", including the opening and closing switches.

*/


So..how are we going to use two of those to switch easily between two snippets of code? Just watch:

Code: ags
/**/ // #1
if (condition == true) Display("Blah!");
/*/ // #2
if (condition == true) Display("Urgh!");
/**/ // #3


1. Here we open and close comment block; we are not inside a comment block
2. This is our "switch"..since we were not in a comment block..now we are
3. Here this only closes the "switch" block that we opened with the switch..but we'll see why we want to have the opening /* there in a minute.

So how does this help us? By changing only one character in the script file, we can quickly and easily change between the two blocks of code:

Code: ags
/*/ // #1
if (condition == true) Display("Blah!");
/*/ // #2
if (condition == true) Display("Urgh!");
/**/ // #3


1. Here we removed one of the asterisks, meaning that we never closed the comment block we opened; we are in a comment block now
2. In this case, since we are in a comment block, the switch actually closes the comment block
3. Finally, since we weren't in a comment block, we find that we needed the opening part here so that we don't get issues due to the closing portion.

With only a single if statement this might be a bit overkill versus just simply using double-slashed line comments..but for large blocks of code (for example if you're working with someone else's code and don't want to corrupt the original source, but want to change/examine it)..this ability to switch between two blocks of code by changing only a single character has been very useful to me personally in debugging scripts.

Ryan Timothy B

Ha. Yup, you've posted that one before and I've found it useful a few times now.

That solution to get the autocomplete working with the pointer to the managed struct is an unbelievably huge quirk with the engine. Hmm.

monkey0506

You're basically just using the preprocessor and the compile-time code to lie to autocomplete..:=

It's sort of hackish, but it's fun to see it in action. :)

SMF spam blocked by CleanTalk