AGS new keywords: Help me sorting it all out

Started by Monsieur OUXX, Mon 18/01/2016 15:42:55

Previous topic - Next topic

Monsieur OUXX

My habits in AGS programming are old, and my brain is still stuck with the features and keywords from 10 years ago. Help me transition to newer AGS programming features!

- what's the difference between protected, writeprotected, and readonly?
- what does "autoptr" mean? (please tell me even if it's not supported?) -- when I google "ags autoptr" I only get results from git, but Monkey uses it all the time.
- please direct me to the post about "managed struct" which I can't seem to find anymore? (EDIT: found this)
- what's the benefit of adding "attribute", inside a struct? (EDIT: OK so apparently this one is not new at all! I've found this.)



So I've found here that managed structs are very limited and cannot contain pointers or dynamic arrays,, and yet for years Monkey has been doing stuff like this :
Code: ags

autoptr managed struct StackData
{
    protected int _idata;
};

struct Stack
{
  protected StackData _items[];
};

void set_Capacity(this Stack*, int value)
{
  StackData items[] = new StackData[value];
  this._items = items;
}

//And then in the code:
...
Stack myStack;
myStack.set_Capacity(100);



What is this wizardry???

 


Monsieur OUXX

 

monkey0506

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55and yet for years Monkey has been doing stuff

Long before Chris Jones even considered opening up the AGS source code, I've prided myself on pushing the limits of what could and could not be done. I discovered a lot of hidden potential, well before it could be seen on Github. :=

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- what's the difference between protected, writeprotected, and readonly?

protected: This struct member can only be accessed from within a struct function (including extender functions) by using the this keyword:

Spoiler
Code: ags
struct MyStruct
{
  protected int bar;
  import void Foo();
};

void MyStruct::Foo()
{
  this.bar = 5; // inside a struct function, using this keyword -- fine
  Display("%d", this.bar); // fine
}

MyStruct myStruct; // instance
myStruct.bar = 10; // outside a struct function, not using this -- will not compile!
Display("%d", myStruct.bar); // not using this, will not compile
[close]

writeprotected: Like protected, except you can get the value from anywhere. Only writing the value is protected.

Spoiler
Code: ags
struct MyStruct2
{
  writeprotected int bar;
  import void Foo();
};

void MyStruct2::Foo()
{
  this.bar = 5; // using this, fine
  Display("%d", this.bar); // fine
}

MyStruct2 myStruct2;
myStruct2.bar = 10; // writeprotected -- will not compile
Display("%d", myStruct2.bar); // reading the value -- fine
[close]

readonly: A readonly member can never be written to, even if using this. It is not useful for regular members at all, and is only useful for attributes.

Spoiler
Code: ags
struct MyStruct3
{
  readonly int bar;
  import void Foo();
};

void MyStruct3::Foo()
{
  this.bar = 5; // will not compile
  Display("%d", this.bar); // fine, but always displays 0 (int default value)
}

MyStruct3 myStruct3;
myStruct3.bar = 10; // will not compile
Display("%d", myStruct3.bar); // fine, displays 0
[close]

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- what does "autoptr" mean? (please tell me even if it's not supported?) -- when I google "ags autoptr" I only get results from git, but Monkey uses it all the time.

autoptr means automatic pointer, and is useful in AGS when working with managed struct types, which are the only struct types in AGS which you can create pointers to. In AGS, literally all this does is prevent you from having to type the asterisk when defining an instance or reference.

Spoiler
Code: ags
managed struct MyManagedStruct // no autoptr
{
  // ... members ...
};

MyManagedStruct *mms = new MyManagedStruct; // create a new instance, and store a pointer to it (with asterisk)

autoptr managed struct MyManagedAutoptrStruct // with autoptr
{
  // ... members ...
};

MyManagedAutoptrStruct mmas = new MyManagedAutoptrStruct; // creates a new instance, and store a pointer to it (without asterisk)
[close]

Literally everything else about the way you use these pointers is the same.

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- please direct me to the post about "managed struct" which I can't seem to find anymore? (EDIT: found this)

I'm not sure if there's a specific post you're trying to recall, but let me explain...

managed struct means that AGS is handling the memory management for the instances of this struct. The latest versions of AGS allow user-defined structs to be dynamically instanciated (e.g., with the new keyword), if they are managed structs (this is a relatively recent change, in the 3.4.* branch). Due to the way the AGS compiler works, a dynamic instance cannot have pointers to other dynamic instances, or else it will leak that memory. This is something that hopefully will be changed in future versions of AGS, but for now, a managed struct cannot have any pointers. The built-in types like GUI are permitted to have pointers via a new builtin keyword, but that is because those pointers are guaranteed to be cleaned up by AGS (things like GUIs and GUIControls aren't even dynamically allocated). A builtin struct cannot be dynamically instanciated (nor can it be statically instanciated either, you can only store a pointer to an existing instance, like gui[0]).

Additionally, I will note that historically managed had similar behavior to the way that builtin works now. Most of the modules in which I used the managed keyword, I did so simply to prevent users from instanciating the struct, rather than to indicate that the user was meant to somehow use pointers to that struct (recent modules being an exception). The above example of autoptr should be sufficient to show how to instanciate a managed struct. Everything else works exactly as you would expect when using a pointer to that struct instance (meaning, yes, managed struct pointers are passed by reference when used as function parameters, and can modify the original object!).

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- what's the benefit of adding "attribute", inside a struct? (EDIT: OK so apparently this one is not new at all! I've found this.)

The thread you linked to, and CW here in this thread, both link to the wiki article I wrote on the attribute keyword. For the record, that keyword has existed and worked exactly the same way since AGS 2.7. In fact, if you use the first method the wiki article describes (importing the get_* and set_* functions directly in the struct definition), then you can use attributes in AGS 2.7, no problem. The functionality hasn't changed. You do seem to still be confused as to what it means though, so I'll try to clear that up.

When you have some member like "int Value;" in a struct definition, you are allocating enough memory for an int named "Value". Every struct instance allocates its own memory, so every struct instance has a separate int named "Value". An attribute works similarly, with the important distinction that it does not allocate memory. If it doesn't allocate memory, then what does a statement like "inst.Value = 5;" mean? Well, instead of allocating memory, an attribute generates a function call. Every attribute has at least one function associated to it, the getter. The getter is not defined for you. You must provide one, or else your scripts that rely on it will fail. My personal preference is to define the getter as an extender method. This prevents having to type an import declaration in the script header, preventing clutter, and it also makes sure that the user doesn't see the function. The user seeing the getter function wouldn't hurt them, but it might be confusing. The getter always takes the name of the attribute, with the prefix get_.

Spoiler
Code: ags
// header

struct MyStruct
{
  import attribute int Value; // note that attributes are always imports; they can be protected, writeprotected, readonly, or none of the above
};

// script

int get_Value(this MyStruct*) // getter for MyStruct.Value; return type is the type of Value, and there are no additional parameters
{
  return SOME_VALUE; // TODO: return some meaningful result...
}
[close]

Bear in mind what I said that the attribute does not allocate memory. So now we have a getter function, but what are we getting? With no memory allocated, we don't have a "Value" to return. This can be useful if the value is some calculated result, or even if you're returning some value stored elsewhere, such as if your struct has a reason to return one of the game options, as returned by GetGameOption. You could make your attribute "Value" work as a synonym for calling GetGameOption.

Since "Value" can be written to, we'll also provide a setter, a function to set the value. This also takes the name of the attribute, with the prefix set_.

Spoiler
Code: ags
// header

struct MyStruct
{
  import attribute bool PixelPerfect; // attributes can be of any type that is valid as a return or parameter type
};

// script

bool get_PixelPerfect(this MyStruct*) // return type for PixelPerfect is bool
{
  return GetGameOption(OPT_PIXELPERFECT);
}

void set_PixelPerfect(this MyStruct*, bool value) // setter: return type is void and takes exactly one parameter of the same type as the attribute
{
  SetGameOption(OPT_PIXELPERFECT, value);
}
[close]

I don't mean to reiterate the entire wiki article here, but I just wanted to show that attributes can be used pretty much any place you would otherwise use a function. The real benefit of attributes comes in the fact that they are used like regular data members, not functions.

Spoiler
Code: ags
MyStruct myStruct;
myStruct.PixelPerfect = !myStruct.PixelPerfect; // toggle pixel perfect


Without attributes, this example would look something more like this:

Code: ags
SetGameOption(OPT_PIXELPERFECT, !GetGameOption(OPT_PIXELPERFECT));


These both do the same thing.
[close]

Their importance becomes more apparent when modifying the existing value. Being able to use compound operators like += or -- is much more efficient than nested function calls.

The wiki article goes more into attributes, of course, but as a final note on them I'll point out that a common use case would be to have an attribute and a protected member with similar names. The attribute could be used as the public access to the protected member. When the user sets some value using the attribute, you can check if the value is valid in the setter, and immediately alert the user if something has gone wrong. If all is well, then you can store the value in the protected member using the this keyword to access the member.

Monsieur OUXX

Thanks a lot for all the effort put into writing this. I hope I'll be able to digest all that without shouting "Oh, fuck that shit" in the process. I really, really want to be able to adapt your stack module to my own purposes.
 

Crimson Wizard

It is worth to mention, that readonly keyword must be the first keyword in the line, otherwise it causes compilation error. This is a bug we are fixing in AGS 3.3.5.

monkey0506

P.S. readonly predates 2.7 (confirmed that it existed in 2.62). Everything else that you asked about (except autoptr) was introduced in 2.7 (protected, writeprotected, managed, and attribute!! attributes can be used by user scripts in 2.7, but the accessors must be imports as extenders didn't come about until 3.0). autoptr was introduce in 2.71, with the String type (which is autoptr). :=

This means that literally everything you asked about is:

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55features and keywords from 10 years ago

Sauce 8-)  "2006-01-07 - ags_271.zip"

Monsieur OUXX

Quote from: monkey0506 on Tue 19/01/2016 15:55:53
This means that literally everything you asked about is features and keywords from 10 years ago
Well I think that demonstrates pretty well how "attractive" they all are, and how much even a regular AGS scripter wants to try diving into this (mostly unsupported) mess. Ten years later I'm still cold-feet about it.
 

Snarky

I'm not sure it demonstrates anything. Or have you been trying to figure this out for 10 years?

Of course, the reason to keep them undocumented and unsupported is precisely to discourage scripters from messing with it, as none of this should be necessary in the course of normal AGS coding, except maybe writeprotected.

Crimson Wizard

#9
attributes are super useful, all the newest built-in OO-types show that. They are basically like properties in C#.
I've been using them in modules I was writing for other people over time. "readonly", "protected" and "writeprotected" are ways to incapsulate things - which is pretty much standard nowadays.

autoptr is kinda specific, and may be confusing, since the rest of AGS uses *. I think it was added mainly for convenience of using String type.

I believe it is a shame this was not added to the Manual. BTW, I created an issue in the tracker about this.

monkey0506

#10
I use all of these features extensively in my modules, and I'm sure I've talked about most of them in some capacity. Things like attributes are definitely not something I'd consider essential to the course of your everyday adventure game project, and yet without them, code such as the following would be impossible:

Code: ags
  ScrollingDialog.ScrollArrows.Float = eFloatRight;
  ScrollingDialog.ScrollArrows.Up.NormalGraphic = 10;
  ScrollingDialog.ScrollArrows.Up.MouseOverGraphic = 9;
  ScrollingDialog.ScrollArrows.Up.PushedGraphic = 10;
  ScrollingDialog.ScrollArrows.Up.Padding.Top = 4;
  ScrollingDialog.ScrollArrows.Up.Padding.Bottom = 2;
  ScrollingDialog.ScrollArrows.Up.Padding.Right = 5;
  ScrollingDialog.ScrollArrows.Down.NormalGraphic = 8;
  ScrollingDialog.ScrollArrows.Down.MouseOverGraphic = 7;
  ScrollingDialog.ScrollArrows.Down.PushedGraphic = 8;
  ScrollingDialog.ScrollArrows.Down.Padding.Top = 2;
  ScrollingDialog.ScrollArrows.Down.Padding.Right = 5;


(That's actual, functioning code I'm working on for A Night at Camp Ravenwood, btw)

And I agree that a feature like attributes deserves an explanation in the manual.

Snarky

Well, coding in Java recently, I find that I don't really miss properties all that much. I must admit that code sample does look nifty though.

monkey0506

If it weren't for the fact that Java allows you to call methods directly on the result of a function, then I'd be inclined to call you a liar. Properties/attributes are really nice when you find yourself using compound operators like ++ or += frequently. Incremental assignment with direct accessor calls is beyond annoying.

Snarky

But how often do you really need to increment member variables outside of the class? I find it's pretty rare, and if there is some variable that you'll need to adjust a lot relative to its current value, you can always add an addToValue() method.

Crimson Wizard

#14
I do not see why it has to be ++/-- reason; I simply like properties/attribute because they make code look simplier; which could easy be a subjective view.
E: what I mean is that with properties you basically write "A" or "A = B", instead of "GetA()" and "SetA(B)".

Perhaps I did not emphasize that enough in my previous answer, but I think access rights (writeprotected, etc) are far more important, especially for non-static / non-singleton user types (in these cases you may find a way to hide internal data inside the script module).

Monsieur OUXX

Well all the above is important but my main concern is really to ba able to design my data structures without realizing at the very end that it's forbidden by AGS because at some point I used a pointer or a struct or a dynamic array inside something else.
 

monkey0506

#16
As a general rule of thumb, if you need to pass it as a function parameter or store it as a struct member, try to use managed struct so you can use pointers. However, managed struct cannot store pointers itself -- including String and dynamic arrays. There are ways of coping with that limitation of no pointers, depending what exactly you need, but that varies greatly on a case-by-case basis.

For example, if you can guarantee that the text won't exceed 200 characters, you can get away with faking a String member of a managed struct by using attributes:

Spoiler
Code: ags
// header

managed struct MyStruct
{
  import attribute String Name;
  protected string name; // where the data is actually stored, protected to prevent unwanted access
  // TODO: protected bool isNull; -- if preserving the "null" String state is important
};


Code: ags
// script

String get_Name(this MyStruct*)
{
  return this.name; // string -> String conversion is automatic
}

#ifdef STRICT_STRINGS
import void StrCopy(string dest, const string source); // import this if "Enforce new-style strings" is selected
// import other old-style string functions as needed, but for the most part you can get by with the new-style String methods and only use StrCopy just-in-time to save the results
#endif // STRICT_STRINGS

void set_Name(this MyStruct*, String value)
{
  if (value == null)
  {
    value = "";
  }
  else if (value.Length > 200)
  {
    // AbortGame("Attempted to set Name of more than 200 characters! Name must not exceed 200 characters max!"); // hard method
    value = value.Truncate(200); // soft method
  }
  StrCopy(this.name, value);
}


Code: ags
// usage

MyStruct *ms = new MyStruct;
ms.Name = "Hello";
ms.Name = ms.Name.Append(" World!");
Display("%s", ms.Name); // displays Hello World!
[close]

Dynamic arrays can't really be worked around in any reasonable way (creating some kind of memory pool would necessitatively eat up more memory to avoid copying/resizing than just using a static array, so it's self-defeating). Custom struct pointers could be worked around using a memory pool, similar to the String cache in the Stack module (but would require manual destruction or the memory pool could grow unnecessarily large). And likewise for Strings if you don't want to enforce the 200-char restriction.

So, there are options, but considering how long AGS got along without even being able to create pointers to custom structs, I'd say that you can probably manage without pointers inside managed structs for the time being...

Crimson Wizard

Well, custom managed structs appeared to be like that totally unintentional. When I found out how "easy" it is to implement them in the engine, I was too excited to check all the consequences. When we found out, these structs were already in released version. So we decided to let them stay (but with a restriction).

monkey0506

Even with the restriction, they're really very useful to have. It allows much more dynamic coding, like the dialog snippet I provided above. The alternative of public fields like ScrollingDialog.DownArrowPaddingTop, or worse, a public function like ScrollingDialog.SetDownArrowPaddingTop(int) seems really nasty in comparison. When we can have pointers as members of managed structs, and hopefully someday features like constructors and destructors, then obviously it will be all the more useful to have. Nevertheless, this (mostly) plugs a long-standing hole in the language, and I, for one, am grateful for that much. :=

Monsieur OUXX

I didn't understand this sentence :
QuoteThe built-in types like GUI are permitted to have pointers via the builtin keyword
Since forever, I've been having pointers to, let's say, GUIButtons, inside my structs (I believe? Or did I fantasize this?).

So where does "builtin" step in? Is it used only in the built-in AGS script headers (not visible in the Editor, obviously), or are you sayin gthat they could be of benefit somewhere else, for the end-scripter?
 

SMF spam blocked by CleanTalk