Very interesting post, most helpful.
1) About my early version of "AsPoint3D"Let's stop talking about that. I suggested that only because I thought this construction was forbidden in AGS script :
Point2D* = AnyFunctionThatReturnsAPoint3D_evenIfPoint3DextendsPoint2D();
I thought that The
GUIControl / AsLabel,AsXXX,... thing was a unique exception, only for GUIControls, built directly in the engine code, and that it couldn't be reproduced even with the use of a plugin.
So I came with that stupid workaround of returning a
copy of the Point2D object, turned into a Point3D. for me it was a poor hack.
I thought "OK, it's not the same object, but if the developer doesn't alter the fields and only works with pointer members, then he might at least be able to do something out of it, and call the functions that expect a Point3D when all he has is something declared as a Point2D".
Anyway - then again, let's stop rubbing my face into it.
2) About overload and override- (a) AGS doesn't typically allow function overloads.
- (b) but overload is possible thanks to extender methods
- (c) you can't have multiple extenders with the same name in the same class
- (d) you can't use an extender to override an existing class method
- (e) (using extenders only) you can overload a function from a base class in the derived class
- (f) (using extenders only) the return type and parameter set can be changed and still override the base class function
Alright, tell me if I misinterpreted some of it:
struct Point3D;
struct Point2D {
import int foo();
import int foo(int a); //forbidden because of (a), and there is already a class function "foo"
};
struct fooStruct {
import int foo(); //allowed (fooStruct is not related to Point2D)
}
struct Point3D extends Point2D {
import int foo(); //forbidden because of (a) (yes, I do know that in that specific case, it's actually override, not overload)
import int foo(int a); //forbidden because of (a) (this foo overloads "foo()" from Point2D and would override "foo(int a)" from Point2D)
};
int foo(this Point2D*) //forbidden because of (d) and there is already a class method "foo" in Point2D
{
//
}
int foo(this Point2D*, int newParam) //still forbidden because of (d) even though the params are different
{
///stuff
}
int foo2(this Point2D*) //allowed (regular extender)
{
//
}
int foo2(this Point2D*, int newParam) //forbidden because of (c), because there is already a foo2
{
//
}
float foo(this Point3D*, int newParam) //allowed because of (e).
//It doesn't matter if the params are the same as in "Point2D::foo()"
{
//
}
int foo2(this Point3D*) //allowed (override of Point2D's foo2, thanks to (e))
//int foo2(this Point3D*, int newParam) //would also work because of (f)
{
//
}
//Imagine this:
Point2D* p = Point3D.Create();
p.foo(66); //it calls the code of "float foo(this Point3D*, int newParam)"
p.foo(); // what happens? Was "Point2D::foo()" definitely overridden by the extender, even with this prototype, or only overloaded?
//if I declared "foo2(this Point3D*, int newParam)":
p.foo2(); //what would this do? Was "int foo2(this Point2D*)" only overloaded by "foo2(this Point3D*, int newParam)", or completely overridden?
Note: I'm going to test the snippet above in AGS. If you don't feel like answering, just ignore the questions.
3) About casting in C++Point2D.AsPoint3D should be acting as a cast. Since C++ pointers are not type safe, I had to look it up, but it seems that using C++'s dynamic_cast will return null if the pointer is invalid:
Point3D* Point3D::Point3DfromPoint2D(Point2D* obj)
{
return dynamic_cast<Point3D*>(obj);
}
If the constructed object stored in the Point2D* is not a Point3D, then this will return NULL.
I'm happy the cast returns null, I wasn't sure if I'd have to store the type internally to check it before casting. It might still be interesting in order to be able to test from the script what is a) the declared class of a variable, 2) The actual class of a variable, 3) all in-between classes it comprises. (e.g. in Java : "instanceof" versus "getClass()").
I'll have to refresh my memories of static_cast versus dynamic_cast, it's always been a bit blurry for me.
4) Keyword "attribute"Regarding attributes, the attribute keyword first showed up in AGS 2.7, and is used by virtually every user-accessible property of every managed AGS type.
(...)
Use of attributes outside of the engine source and/or plugins is not supported, although it is fully functional.
The reason why I was asking is because I was hoping to test attributes behaviour in a regular-ass script, and wasn't sure if I'd have to implement getters and setters, and if that would work outside of a plugin.
The example you gave ("struct Attributes { ... };") shows it does.
5) Registering getters and settersIf you're writing a plugin, you would register accessor methods like this:
engine->RegisterScriptFunction("Point2D::get_AsPoint3D", Point2D_get_AsPoint3D);
It's already doing it, mimicking the tutorial of Calin.
6) Keywords "readonly", "writeprotected"As an example of accessors in native AGScript:
struct Attributes { ... } ;
Thanks a lot for this, now I know what keywords to expect and how to implement them.
There are a few things I'm not sure I fully understood :
1. Why do you always declare attributes in double ("Strings" versus "strings", "Float" versus "_float") ? Can't you set directly "Strings[ x]" and "Float" in their respective setters? Oh wait. Is this a workaround for what you said : "If you define an attribute in AGScript, you
cannot use the attribute in the same script as which the accessor methods for it exist."?
2. The difference between readonly and writeprotected -- for that I'll check the manual/wiki. So far I understand that in both cases, the value is not set, but that in the case of writeprotected, the function is still called (in case there is some side processing to perform). But then why did you set the value of "Attributes_writeprotectedStaticInt" in the setter? Because of that, in the end it behaves as a regular attribute doesn't it?
3. Shouldn't this take a String instead of an int? "void seti_Strings(this Attributes*, int index, int value)"
7) WikiFinally, please feel free to read Extender Methods mean Polymorphism! and Keyword: attribute in the wiki. That's kind of why I put them there. 
Yes, I will! Thanks for the links! It's always a bit awkward to know where to search: Manual or wiki -- or even to remember the wiki exists. I miss almost 100% of the wiki articles, even when searching with Google.
The wiki doesn't have a very good visibility on the website/forums, and it's a pity because it contains invaluable information.
A bit off-topic, but do you have a suggestion to bind it more efficiently from the new HAT? (example: Having an additional folder in the "contents" of the help file. This folder would contain the wiki articles, but their pages would just say: This article is not an official AGS article and is hosted on the internet. Click on this link to open it in your web browser" -- this solution would also allow to add the keywords to the help index).
8) Attributes "AsXXX"I now have a clearer vision of how that should be implemented in C++.
The only thing that concerns me is how to decide programmatically what "AsXXX" should be added to the base class.
Indeed, Point2D can have many children classes. That means I need to compute a list of all children classes, and then add the corresponding "AsXXX" to the base class.
Also I still haven't decided if "AsXXX" attributes should be added explicitly by the developer to the script definition*, or if they should be added automatically in the last moment to the script header.
Maybe a hybrid version would be to add automatically all the "AsXXX" that can be computed directly from the current script definition*, but also allow the developer to add more manually in the very same definition*, in order to anticipate other plugins that co-exist with this one.
* I'm talking about the AGS pseudo-code that's being fed to EasyPlugin -- not the output script header.