AGS Easy Plugin (v0.8a): Create a full engine plugin in one click-TESTERS NEEDED

Started by Monsieur OUXX, Mon 18/06/2012 02:40:35

Previous topic - Next topic

Monsieur OUXX

That tutorial by Calin gave me the following idea:




What does it do?
- You feed this small command line program with a single, tiny file containing one or more "struct" definitions, in AGS syntax.
- It generates all the files you need to create the corresponding plugin.

Basically you won't have to worry about those things anymore: Copy-pasting one billion getters/setters, remembering the names of the interfaces, registering your functions in the plugin, etc.
Easy Plugin does it for you!



Can I try it?
Yes. If you've been good (or if you're as handsome as Dualnames).
- You need a Java Virtual Machine installed. If you don't have one, go get one : www.java.com/en/download/ (Ags Easy Plugin was tested against Java 6 (update 32) 32-bits, on a Windows 7 professional 64-bit -- it means it should also work with Java 7, possibly 64-bits flavour).
- Download the example :
.
- Run the file test.bat

You will find all the files produced in the "output" subdirectory. Yes, that's how much boring typing Easy Plugin spared you.

Why do I get Warnings when I run the example?
The example shows you that Easy Plugin also warns you when you forget to declare a "Create" function in your struct, or when the types don't match.


OK, now I see what it does. Can I use it for my own project?
Yes, simply edit the contents of the file "input.easypluginDefinition".
You can try to run AGSEasyPlugin.jar without parameters to see the expected command line parameters.


I want to change the way it works. Is this Open Source?
Yes. And VERY easy to modify for your own needs.
- What you need is to follow the steps described in this video : http://vimeo.com/groups/29150/videos/8001326 It takes no more than 10 minutes to set up the whole environment!
IMPORTANT: I didn't manage to have ANTLR IDE work with the latest versions od Eclipse and ANTLR. I had to use Eclipse Galileo SR1 (32 bits) and ANTLR 3.2. Apart from that, you can use the latest versions of the libraries shown in the video.
- Download the project files : http://shutupload.com/dl/407f7aba991f/
But you don't need to get the source code to use AGS Easy Plugin. As it is, you can just run it.


Why Java?
- Because it's almost a clone of C# except you don't have to go through the hassle of having a copy of Visual C# (even with the express edition you have to register)
- Because AGS is moving towards being cross-platform. This program would work on Mac or UNIX with minor changes.
- Because I'm sure you won't need some redistributable package to make it work.


HELP WANTED
At the moment it works pretty well, but you can help me improving it by toying with it and telling me if I forgot something.
For example, I was rather focused on automating the output, but I might have produced semantically wrong code in C++ (forgotten a function, etc.)

 

monkey0506

Quote from: Monsieur OUXX on Mon 18/06/2012 02:40:35- You need a Java Virtual Machine installed. If you don't have one, go get one : www.java.com/en/download/
Quote from: Monsieur OUXX on Mon 18/06/2012 02:40:35- Because I'm sure you won't need some redistributable package to make it work.

So, which one is it? :P Not to say most users probably won't have the Java VM installed, but that's just as much a "redistributable package" as the .NET Framework.

Other than that, it looks pretty nice (without having tried it out). A couple things come to mind though...

What if I define my AGS structs like this?

Code: ags
managed struct Point3D; // forward declaration of struct

managed struct Point2D
{
  import attribute Point3D *AsPoint3D;
  import attribute int X;
  import attribute int Y;
  readonly import static attribute int DefaultX; // $AUTOCOMPLETESTATICONLY$
  readonly import static attribute int DefaultY; // $AUTOCOMPLETESTATICONLY$
  import static Point2D* Create(); // $AUTOCOMPLETESTATICONLY$
};

managed struct Point3D extends Point2D
{
  import attribute int Z;
  readonly import static attribute int DefaultZ; // $AUTOCOMPLETESTATICONLY$
};


I've added the Default* properties because otherwise the Create function would have to be overloaded on the Point3D class (which AGS doesn't allow), or it would have to have an unnecessary and unused third parameter on the base class.

Does your exporter allow derived structs? It absolutely must do so.

Although it shouldn't hurt anything, would your exporter link a setter function for the Default* properties? Because AGS would never call it.

I'm also interested to see if the $AUTOCOMPLETESTATICONLY$ comment tags would properly carry over into the C++ scripts.

Another interesting point about this would be the fact that with extender methods, simple polymorphism is possible...which your exporter could take advantage of. Could be a bit messy to actually incorporate on the plugin side of things, or at least as part of your automated tool. Would still be fun to see that in action.

In any case, good work (again, said without testing it out :D).

Edit: I'm not saying that this particular inheritance is an especially good one...but I think it gets the point across (ignore the fact that you'd have to cast the pointer every time you create a Point3D :P).

Calin Leafshade

This is nice.

One of the reasons that i havent made some more plugins with some simple types (like Point and Size and StringList and so on) is because its such a ball ache to create the classes and the boiler plate.

Monkey also makes excellent points.

Also, i would *really* consider C# here (superior string manipulation) or, since its just processing, python or something. But thats really just personal preference.

Monsieur OUXX

Quote from: monkey_05_06 on Mon 18/06/2012 05:25:21
So, which JVM is it? :P
Edited first post.

Quote from: monkey_05_06 on Mon 18/06/2012 05:25:21
that's just as much a "redistributable package" as the .NET Framework.
Yes, but the one big difference is that you often realize afterwards that you're missing a redistributable package. I hate that. Like, you have a cryptic error message about a missing DLL, and you post about it on the forum, and you're being answered: "Oh, do you have whatever redistributable package? I would have shipped it with the app but it's legally forbidden by Microsoft's license" :/ I prefer to say FIRST install Java (any Java!) and THEN you're 99% certain it will work.

Quote from: monkey_05_06 on Mon 18/06/2012 05:25:21
Didn't try it
Please do!!! I want to make sure it will work out-of-the-box on most systems.

Quote from: monkey_05_06 on Mon 18/06/2012 05:25:21
A couple things come to mind though...
Please list some specific constructions that you'd like the internal grammar to recognize and process AND what it should give in C++. I'm not comfortable enough to infer the expected C++ output and then realize I was all wrong (for example, what should readonly attributes become in C++?)
I'll implement them. The only exception might be "// $AUTOCOMPLETESTATICONLY$" because maintaining comments is a pain.


Quote from: Calin Leafshade on Mon 18/06/2012 06:31:25
since its just processing
I'm not sure what you mean. That sort of compiler is getting away from basic text swapping, and relies instead on building grammar trees, which ANTLR does, independently from the language. It could easily produce a C# code, but I've chosen Java by design. I admit that struct definitions are rather linear, so building grammar trees is a bit overkill, but it's a warmup for more complex syntactic tools to come.

OK guys, now start making engine plugins already! ;-)

EDIT:

v0.8a :
Everything you can see in the example's screenshot, in the first post

Target for v0.9a :
Readonly attributes...............DONE (not in downloadable version yet)
static attributes.....................DONE (not in downloadable version yet)
forward declaration...............DONE (not in downloadable version yet)
AutoComplete........................DONE (not in downloadable version yet)
Attributes default value.........DONE (not in downloadable version yet)
Inheritance............................WiP
Generation of Arrays code.....NOT STARTED



 

Monsieur OUXX

QUESTIONS
1) forward declarations
I'm a bit embarrassed for the forward declarations.
How does "editor->RegisterScriptHeader(myStructScriptHeader);" work? Every time I call this function, does it concatenate "myStructScriptHeader" to any other header previously registered, making it one big script header for the plugin?
At the moment, Easy Plugin only works with structs definitions (as opposed to forward declarations) making one script header per struct. I wouldn't know where to fit a simple struct declaration.

Could you give me an example of what you expect to find in the C++ files regarding forward declarations?

2) Default attributes
I didn't understand what you were talking about with "DefaultXXX". Do attributes starting with "Default" have a special status in AGS plugins?
 

monkey0506

AGS only allows forward struct declarations for the purpose of declaring pointers to them. You can't create an instance based on a forward declaration.

I would say in most instances, it can be conceived that the forward struct declaration should be included in the script header of the struct definition immediately following it. In this case, Point3D's forward declaration would be registered in the Point2D header string.

As far as the C++ side, it allows forward declaration of classes. So I would expect that.

Regarding the "Default*" attributes I added, that was just as part of the example. They would just be a friendly way to determine what the default values populated by the Create method would be (seeing as I used a parameterless Create method, for the reasons stated above). They're not special in any way. Apart from being readonly and static which apparently wasn't previously supported. :P

Regarding the readonly attributes, I could give a crap less if they were fully read/write on the C++ side, but on the AGS side you only need to register a getter, not a setter.

You said that you implemented the $AUTOCOMPLETESTATICONLY$ comment tag...what about $AUTOCOMPLETEIGNORE$ and $AUTOCOMPLETENOINHERIT$? :D

And I guess we'll just have to agree to disagree about the "redistributable package" because I honestly don't understand the validity of using that as the argument (portability, etc. yes, but not just ignoring the fact that external redistributable packages are required either way).

Oh, and 99% of the time, I will prefer native code over engine plugins, because it's automatically portable (doesn't require platform-dependent rebuilds) and is therefore easier to maintain.

Monsieur OUXX

Quote from: monkey_05_06 on Mon 18/06/2012 12:38:25
AGS only allows forward struct declarations for the purpose of declaring pointers to them. You can't create an instance based on a forward declaration.
I would say in most instances, it can be conceived that the forward struct declaration should be included in the script header of the struct definition immediately following it. In this case, Point3D's forward declaration would be registered in the Point2D header string.

I understand forward declaration, but you didn't answer my question: How many "char*" script headers do I need in the plugin? One with all the structs, or several (one for each struct)?  In case there is only one, can I still register it little bit by little bit?
 

monkey0506

As far as I can tell you could register it all at once, which would be equivalent to putting it all in the same ASH file, or you can register it separately, which would be like putting it in separate ASH files. The order in which they're registered would indicate which header was considered "on top" or first. So as long as the forward declaration is registered before the struct referencing it then it should be fine.

...I'm not sure why that was unclear before...so hopefully I've clarified that?

Monsieur OUXX

Quote from: monkey_05_06 on Mon 18/06/2012 15:12:51
...I'm not sure why that was unclear before...so hopefully I've clarified that?

Yes thanks. That was unclear because I've never written an engine plugin in my life, and believe it or not it takes a little time to get the whole picture :)
 

Monsieur OUXX

Double-posting to bump a question to Monkey.

You wrote : "I'm also interested to see if the $AUTOCOMPLETESTATICONLY$ comment tags would properly carry over into the C++ scripts."

I thought $AUTOCOMPLETExxx$ special comments were only for AGS scripts? I've propagaed it to the script header in the plugin, but I have no idea how to achieve autocomplete in Visual Studio.
I'm going to Google it, but do you have suggestions?

 

monkey0506

Well you're registering the AGS script header in C++ code, aren't you?

What I mean is that I wanted to be sure the tag would be persisted here:

Code: cpp
const char *header = "import void DoSomething(); // $AUTOCOMPLETEIGNORE$"; // << the $AUTOCOMPLETEIGNORE$ tag is part of the AGS script header


It bears no relevance in Visual Studio. There may or may not be a way of doing something similar, but what really is the point there? The point on the AGS side is to prevent the end-user from calling code that they shouldn't be. If it's in the plugin, they're only going to call whatever is exposed on the AGS side, so it works the same.

I only brought it up because I wasn't sure if your exporter was using the raw input directly or whether it was generating the headers from what it parsed. Based on what you've said about registering different headers for each struct, I would assume the latter, which means the exporter would have to parse the commented tag in the input, and then generate the same tag in the output.

Monsieur OUXX

> I wanted to be sure the tag would be persisted here:

So it's a yes: After your comment, I made it persist, along with the 2 others you mentioned.
It doesn't keep other comments, but it keeps special comments (I have also added special comments formatted like /** this **/ that will allow to initialize the default values (DEfaultX, etc.) directly from the original pseudo-AGS struct definitions file. That's going to be good. Oh yes.


============


Regarding Polymorphism, here is how I see it :
(I wrote the code below quickly from the top of my head, so please pardon any syntax error or C++ errors or confusion between pointers (->) and normal attributes (.) )


ORIGINAL PSEUDO-AGS FILE
Code: AGS

    managed struct Point3D; // forward declaration of struct
     
    managed struct Point2D
    {
      readonly import attribute Point3D *AsPoint3D; //any attribute that matches the pattern "Type* AsType" will be forced to be readonly and will have an auto-generated getter for type casting
      import attribute int X;
      readonly import static attribute int DefaultX /** = 66 **/; // $AUTOCOMPLETESTATICONLY$ //note the special comment /** **/
      import static Point2D* Create(); // $AUTOCOMPLETESTATICONLY$  // ONLY STRUCTS THAT DO NOT INHERIT FROM OTHER STRUCTS ARE ALLOWED TO HAVE AN EXPLICIT "CONSTRUCTOR" LIKE "Create".
    };
     
    managed struct Point3D extends Point2D //Point3D inherits from Point2D. It's forbidden to have an explicit "Create" constructor
    {
      import attribute int Z;
      readonly import static attribute int DefaultZ  /** = 77 **/; // $AUTOCOMPLETESTATICONLY$ //note the special comment /** **/
      //Point3D inherits from Point2D, so there will also be an auto-generated "Point3D* Point3DfromPoint2D(Point2D*)" method in the final files, used by Point2D's "AsPoint3D"
    };


Point2D.h
Code: AGS

    class Point2D
    {
      public:
        int X;
        static int DefaultX; // $AUTOCOMPLETESTATICONLY$ //as per C++ syntax, value is initialized in the .cpp
        Point3D* AsPoint3D();
        Point2D();
        ~Point2D();
    }


Point2D.cpp (fragment)
Code: AGS

(...)

Point2D::Point2D()
{
    this->X = Point2D->DefaultX;
}

(...)

Point2D::DefaultX = 66;

Point3D* Point2D::AsPoint3D()
{
    //we use the utility method defined in Point3D : "Point3D* Point3DfromPoint2D(Point2D*)"
    return Point3D.Point3DfromPoint2D(this);
}



agsplugin.cpp
Code: AGS

(...)

//just to mention that AsPoint3d* is a readonly attribute in AGS, but an actual method in C++
Point3D* Point2D_get_AsPoint3D(Point2D* obj){
   return obj->AsPoint3D();
}
(...)


Point3D.h
Code: AGS

    class Point3D : Point2D
    {
      public:
        int Z;
        static int DefaultZ; // $AUTOCOMPLETESTATICONLY$ //as per C++ syntax, value is initialized in the .cpp

        Point3D();
        ~Point3D();
        Point3D* Point3DfromPoint2D(Point2D*);
    }



Point3D.cpp (fragment)
Code: AGS

(...)

Point3D::Point3D()
{
    //X is initialized in Point2D's constructor
    this->Z = Point3D->DefaultZ;  
}

(...)

Point3D::DefaultZ = 77;

Point3D* Point3D::Point3DfromPoint2D(Point2D* obj)
{
    Point3D* castObject = new Point3D();
    
    castObject->X = obj->X; //we must do this for all attributes of obj
    return castObject;
}


PS: I'm thinking of generating by default an Array class for every struct declared. This way, the end-scripter can store any object defined in your plugin's script header without having to go through the hassle of asking you to implement it.
 

monkey0506

I don't see anything polymorphic about that. You do understand the difference between polymorphism and inheritance, yes?

AGS allows simple polymorphism due to the fact that an extender method of a derived class can silently override any base class method (although I don't think it works for overriding accessor functions). So it is perfectly legal to define an extender method such as:

Code: ags
void BringToFront(this Label*)
{
  AbortGame("No.");
  GUIControl *gcthis = this; // get a base class pointer
  gcthis.BringToFront(); // call base class method
}


Then if you call:

Code: ags
lblStatusline.BringToFront();


Your game would explode. Essentially this is equivalent to marking every method of every base class as virtual, and extender methods of derived classes as override. AGS just does it implicitly. It's probably a quirk, and definitely not "by convention", but it's technically feasible and CJ himself never seemed to think that there would be anything wrong with using it (aside from the fact that it's "unsupported" and could possibly be broken in future versions, which used to go for a lot of things, like arrays and structs lol).

Oh, and why did you put the $AUTOCOMPLETESTATICONLY$ tag in the C++ source? That doesn't belong (read as: do anything) there. :P

And I was thinking about it, that Point2D.AsPoint3D member should definitely be marked as $AUTOCOMPLETENOINHERIT$ as Point3D.AsPoint3D is just redundant. :D

Also, isn't it typically conventional for derived class constructors to have an overload that takes a base class member as a parameter to handle copying member data? If C++ pointers were type-safe then you could simply recast the pointer, but they aren't so...

Edit: I've modified the above example of polymorphism to include calling base class methods from overridden methods.

Monsieur OUXX

Quote from: monkey_05_06 on Wed 20/06/2012 16:59:02
I don't see anything polymorphic about that.

Well, I built my code on your illustration of polymorphism. I thought you what you called polymorphism was the possibility of using .AsPoint3D whenever the scripter wants to pass a Point2D to a method that requires a Point3D.
If that's not what you meant, then please give a code snippet to illustrate what code you want 1) recognized, and 2) Produced!
Preserving motivation is really not your skill :)
 

monkey0506

Quote from: Monsieur OUXX on Wed 20/06/2012 17:04:23Well, I built my code on your illustration of polymorphism.

I never said that the example of inheritance I gave was polymorphic:

Quote from: monkey_05_06 on Mon 18/06/2012 05:25:21I'm not saying that this particular inheritance is an especially good one...

Another interesting point about this would be the fact that with extender methods, simple polymorphism is possible...which your exporter could take advantage of. Could be a bit messy to actually incorporate on the plugin side of things, or at least as part of your automated tool. Would still be fun to see that in action.

Quote from: Monsieur OUXX on Wed 20/06/2012 17:04:23I thought you what you called polymorphism was the possibility of using .AsPoint3D whenever the scripter wants to pass a Point2D to a method that requires a Point3D.

If the constructed object is a Point2D then AsPoint3D should always be null, so this doesn't even make sense. A Point3D could be constructed from a Point2D, but that doesn't mean that AsPoint3D should be creating a new object. AsPoint3D should only return a valid pointer if the constructed object is a Point3D to begin with.

Code: cpp
class Point2D
{
  // ...
};

class Point3D : Point2D
{
  // ...
};

Point2D *p2d = new Pointer2D(); // valid
Point3D *p3d = new Pointer3D(); // valid
Point2D *p2dto3d = new Pointer3D(); // valid
p3d = static_cast<Point3D*>(p2dto3d); // valid
p3d = static_cast<Point3D*>(p2d); // invalid, pointer is now misaligned and will cause memory leaks or other fatal issues


This is not polymorphism. This is inheritance.

Quote from: Monsieur OUXX on Wed 20/06/2012 17:04:23If that's not what you meant, then please give a code snippet to illustrate what code you want 1) recognized, and 2) Produced!

"what I meant" and "what I called polymorphism" were the same thing: polymorphism.

This is a programming concept. I wasn't aware I would be required to teach you what polymorphism is. I have provided a code snippet (in my last post) of polymorphism in AGS.

Quote from: Monsieur OUXX on Wed 20/06/2012 17:04:23Preserving motivation is really not your skill :)

I don't mean to sound cross or anything, but perhaps I'm making too many assumptions about your skills as a programmer. I don't consider myself very advanced (in terms of languages like C++), but you seem to be asking a lot of very basic questions. I'm not upset at you, but I don't think you have a right to be upset at me for assuming you would know what polymorphism is.

Monsieur OUXX

Wow, the link to wikipedia was really the final nail in the coffin. I'm just offering a tool that saves typing.

I'm not saying you're assuming I know what polymorphism is, and I'm not sure why you think I'm upset at you. But I'm asking how you'd see polymorphism working (in concrete situations) on every object produced by the plugin, taking in account AGS' limitations (e.g. the impossibility to overload functions). The examples you just gave are a step forward towards that.

In the case of AGS, "polymorphism" remains a vague notion, and is not universal. That's why I'm asking questions. I'm trying to meet your request, and to offer polymorphism as a bonus. To do that I must match AGS limitations with C++ possibilities, and avoid the syntactic tricks of AGS that only you know (I'd have never known that extenders can be overridden but not getters and setters).

===

On with the technical discussion

Quote from: monkey_05_06 on Wed 20/06/2012 17:20:53
Point2D *p2dto3d = new Pointer3D(); // valid in C++
p3d = static_cast<Point3D*>(p2dto3d); // valid in C++

But this type of behaviour is not implemented in AGS script (or is it?). That's why I suggested .AsPoint3D. It's "by copy", which doesn't make much sense (as you said it should work directly on the original object), but it's better than nothing as long as the scripter doesn't change the attributes values...
But I realize that it was silly now. Yet, I'm still not sure how you picture things. Should the getter of every attributes in a Point2D first check if the AsPoint3D attribute is set , and in that case return the attributes of that object? (the attributes of the Point2D object would remain unused).

===

> why did you put the $AUTOCOMPLETESTATICONLY$ tag in the C++ source?
Because I don't care. I'm just copy-pasting stuff around, and trying to focus on the important things.

===

Attributes : do they exist in AGS scripts? I can't find them in the help file. Neither can I find getters and setters. But I also know that I often have trouble finding the newest 3.x scripting stuff in the help file. So I'm not sure if it effectively does something to use keyword "attribute" in a regular module.
 

monkey0506

I linked to Wikipedia because you seemed baffled by my use of the term "polymorphism", and I don't know what you know (just as you don't know what I know). I'm still trying to sort that out (as, I'm sure, are you).

In terms of AGS polymorphism is limited by the fact that AGS doesn't typically allow function overloads. However, as I showed above, in the case of derived classes, it is possible thanks to extender methods (but only overloading a function from a base class in the derived class, you can't have multiple extenders with the same name in the same class, or use an extender to override an existing class method). So that covers the basic principle of polymorphism that the derived class can have an overload of a function with the same name, return type, and parameter set (although in the case of AGS the return type and parameter set can be changed and still override the base class function). Another principle of polymorphism that AGS doesn't cover is that if the constructed object is a derived class object, in true polymorphic classes, the derived class method would be called when the virtual method is called on the base class pointer. AGS doesn't allow that, although it works in our favor as calling the method from the base class pointer is presently the only way in AGS to call the base class function if the derived class overloads it.

Regarding pointer casting, AGS will allow you to cast a derived class object into a base class pointer. That is always allowed with inheritance (in every language I've seen):

Code: ags
GUIControl *labelAsGC = lblStatusline;


This works fine because the Label class is derived from GUIControl. AGS does not currently have a way to perform the pointer cast in the opposite direction though, which is why I suggested adding a Pointer3D* into the Pointer2D class. It's also the reason why GUIControl has AsButton, AsLabel, AsListBox, etc.

When the C++ Point3D object is constructed, it inherently is a Point2D object as well, just with some additional properties added. So, you do not need to be copying the data from a Point2D into a newly constructed Point3D every time Point2D.AsPoint3D is called. 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:

Code: cpp
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. That is, if I do this:

Code: ags
Point2D *p2d = Point2D.Create();
Point3D *p3d = p2d.AsPoint3D;


Then p3d should always be null. With what you had, it would be constructing a new Point3D and returning that, which is not desirable behavior in this case (what if GUIControl.AsLabel was constructing new labels all the time??).

Having the autocomplete tags in the C++ source doesn't matter. Leave it or don't, I just wanted to make sure you were clear (since you previously asked). ;)

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 (with some exceptions that were excluded, primarily for legacy reasons). These attributes act virtually the same as a property in C#, and are simply encapsulation for underlying data. If you're writing a plugin, you would register accessor methods like this:

Code: cpp
	engine->RegisterScriptFunction("Point2D::get_AsPoint3D", Point2D_get_AsPoint3D);


All this is doing is registering a function with the AGS engine. The exact same effect could be achieved by importing the "get_AsPoint3D" method as part of the struct, or by defining an extender method for the class with the same name. The function would be linked at compile time, based on whether it was registered by the plugin, by a struct import, or by an extender method. Use of attributes outside of the engine source and/or plugins is not supported, although it is fully functional. Several of my modules depend on it inherently, but then again, so does most of the AGS engine.

As an example of accessors in native AGScript:

Code: ags
struct Attributes
{
  readonly import static attribute int ReadonlyStaticInt;
  writeprotected import static attribute int WriteprotectedStaticInt;
  import static attribute int StaticInt;
  readonly import attribute float ReadonlyFloat;
  protected float readonlyFloat; // backing field
  writeprotected import attribute float WriteprotectedFloat;
  protected float writeprotectedFloat;
  import attribute float Float;
  protected float _float;
  import attribute String Strings[];
  protected String strings[50];
  readonly import attribute int StringCount;
};

int Attributes_readonlyStaticInt; // backing field
int Attributes_writeprotectedStaticInt;
int Attributes_staticInt;

int get_ReadonlyStaticInt(this Attributes*) // linked at compile time as the getter; return type matches type of attribute
{
  // technically this is being called statically, so use of the 'this' pointer here is undefined
  return Attributes_readonlyStaticInt;
}

// no setter required (or called) for readonly

int get_WriteprotectedStaticInt(this Attributes*)
{
  return Attributes_writeprotectedStaticInt;
}

void set_WriteprotectedStaticInt(this Attributes*, int value) // setter is required for writeprotected (as it can actually be called)
{
  // feel free to validate value, just as you would with any accessor function
  Attributes_writeprotectedStaticInt = value; // you could omit this to treat it as readonly, but then it could only be set by the backing field directly
}

int get_StaticInt(this Attributes*)
{
  return Attributes_staticInt;
}

void set_StaticInt(this Attributes*, int value) // the name 'value' isn't required, but I like it as a type of convention
{
  Attributes_staticInt = value;
}

float get_ReadonlyFloat(this Attributes*) // this is being called on an instance, so 'this' is valid; again, return type matches
{
  return this.readonlyFloat; // return backing field
}

float get_WriteprotectedFloat(this Attributes*)
{
  return this.writeprotectedFloat;
}

void set_WriteprotectedFloat(this Attributes*, float value) // note that type of 'value' will always match type of attribute
{
  this.writeprotectedFloat = value;
}

float get_Float(this Attributes*)
{
  return this._float;
}

void set_Float(this Attributes*, float value)
{
  this._float = value;
}

String geti_Strings(this Attributes*, int index) // notice for indexed properties the name is 'geti' and it takes an index parameter (again, name is irrelevant but conventional)
{
  // bound checking?
  return this.strings[index];
}

void seti_Strings(this Attributes*, int index, String value)
{
  this.strings[index] = value;
}

int get_StringCount(this Attributes*)
{
  return 50;
}


Warning: If you define an attribute in AGScript, you cannot use the attribute in the same script as which the accessor methods for it exist. Doing so will cause your game to violently explode. And you will die. Violently. You can use them in any later script where they are defined (so if the struct is in a header, any later script), just not the one with the accessor methods. Or any script prior to the one with the accessor methods.

Usage would be the same:

Code: ags
Attributes a;
a.Strings[12] = "Hello World";


Finally, please feel free to read Extender Methods mean Polymorphism! and Keyword: attribute in the wiki. That's kind of why I put them there. :P

Monsieur OUXX

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 :
Code: ags

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

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07

  • (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:
Code: ags


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++

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
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:
Code: cpp
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"


Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
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 setters

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
If you're writing a plugin, you would register accessor methods like this:
Code: cpp
	engine->RegisterScriptFunction("Point2D::get_AsPoint3D", Point2D_get_AsPoint3D);

It's already doing it, mimicking the tutorial of Calin.


6) Keywords "readonly", "writeprotected"

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
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) Wiki
Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
Finally, please feel free to read Extender Methods mean Polymorphism! and Keyword: attribute in the wiki. That's kind of why I put them there. :P
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.
 

monkey0506

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:52Very interesting post, most helpful.

You're welcome. :)

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:521) 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 :
Code: ags

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........

The reason the GUIControl.As(Button/Label/ListBox/...) properties exist is because AGS allows casting pointers to a base class, but not to a derived class:

Code: ags
GUIControl *gc = lblStatusline; // works fine, every Label is also a GUIControl
Label *lbl = gc; // does NOT work, not every GUIControl is a Label (and AGS has no explicit cast operators)


So the AsXXX properties would exist only for casting a base class pointer back into a pointer to the underlying derived class object (if it is indeed a derived class object and not a base class object). Not trying to rub this in your face or anything, just want to make sure it's clear what it's for (and why I included the AsPoint3D in my original example code).

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:522) About overload and override

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07

  • (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

Code: ags
//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?

Interesting points, I don't think I actually fully tested them, but my gut instinct would be that they have been completely overridden and the parameterless methods would in this case be inaccessible. One note though, you cast the Point3D into a Point2D pointer. In AGS this means that the base class functions would be called, not the derived class functions (again, this is something that goes against true polymorphism, but CJ never intended this behavior to begin with :P).

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:523) About casting in C++

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
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:

Code: cpp
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.

Regarding type checking, you could look into using typeid and/or dynamic_cast. static_cast is capable of performing unsafe casts, returning a pointer to an object that doesn't exist or is of a completely different and incompatible type. dynamic_cast is used to prevent this by checking the run-time type and returning NULL if the cast is invalid (or if you're casting a reference instead of a pointer, it would throw a bad_cast exception). CPlusPlus.com has an article on typecasting in C++. Note that, as also noted there, dynamic_cast and typeid both rely on Run-Time Type Information (RTTI) which may be turned off by default in some compilers. Shouldn't be an issue for the end user to turn it on though (as far as I can tell).

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:525) Registering getters and setters

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07If you're writing a plugin, you would register accessor methods like this:
Code: cpp
	engine->RegisterScriptFunction("Point2D::get_AsPoint3D", Point2D_get_AsPoint3D);

It's already doing it, mimicking the tutorial of Calin.

Of course, just explaining the process, trying to make it clear where I was going.

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:526) Keywords "readonly", "writeprotected"

Quote from: monkey_05_06 on Wed 20/06/2012 20:57:07
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.

No problem! :)

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:52There 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."?

No, this is entirely unrelated. Think about it, when you're designing the plugin, you're linking the accessor functions directly to C++ methods. Those methods themselves are not ints or floats, they don't, themselves, store the data. Attributes in AGS are the same as properties in C# - they don't store anything, they only provide the accessors. I provided the protected (non-attribute) data members in the same fashion as you would define a field in C# - a place to store the data for use with the attribute. Without this, the struct would have the accessor methods, but no actual variables associated with them.

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:522. 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?

I covered this in my article in the wiki. Basically if an attribute is defined in AGS as readonly then it is just that. It can never be written to in AGS. Ever. Not even by members/methods of the same class. If an attribute is defined in AGS as writeprotected then outside the class, you do not have any write access. However, members/methods within the class (including extender methods) can still write to it. Therefore, if an attribute is writeprotected, then it must have a setter linked.

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:523. Shouldn't this take a String instead of an int? "void seti_Strings(this Attributes*, int index, int value)"

Yes, that was a typo. ;)

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:52A 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).

I think that we should automate a way of generating the wiki version of the manual, and then set edit rights to the page. Anyone could still edit the discussion page, but the wiki articles themselves should be protected. I think that this would be the best way of going about this.

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:528) 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.

IMO, the struct being fed into your exporter should be the full AGS class. You shouldn't be expected to glean information like this out of nothing. If I know I'm going to be defining some derived classes and that AGS has no explicit cast methods, then it becomes my responsibility as the author of the plugin, not yours as the author of the exporter tool, to define the classes appropriately.

Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:52Maybe 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.

Well of course the end-user could go back and manually enter other AsXXX properties (or anything) into the script headers that are being registered, but again I think this falls beyond the scope of things you should be accounting for, and falls into the hands of the user.

Monsieur OUXX

Quote from: monkey_05_06 on Thu 21/06/2012 17:26:43
Quote from: Monsieur OUXX on Thu 21/06/2012 15:31:52There 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."?

No, this is entirely unrelated. Think about it, when you're designing the plugin, you're linking the accessor functions directly to C++ methods. Those methods themselves are not ints or floats, they don't, themselves, store the data. Attributes in AGS are the same as properties in C# - they don't store anything, they only provide the accessors. I provided the protected (non-attribute) data members in the same fashion as you would define a field in C# - a place to store the data for use with the attribute. Without this, the struct would have the accessor methods, but no actual variables associated with them.

OK, I understand why you had to do it in the example written in AGS script. But in the plugin, I'll just make the utility member hidden in the C++ class, to avoid confusing the end-scripter with 2 homonymous members. The coder can still make it exposed if he wants before he compiles the plugin, by adding it to the script header and all.

===

I'm still not sure how I'll have a factory/pseudo-constructor for Point3D objects, as the name "Create" will be already taken by Point2D. Unless the overloading/overridin restriction doesn't apply to static member functions?
The easy solution would be to have Point2D::CreatePoint2D and Point3D::CreatePoint3D, but it's not very elegant.


 

SMF spam blocked by CleanTalk