Does anyone have any examples of plugins that export managed types?
Can managed types exported from a plugin be made to work more like the intrinsic types? Can the script engine handle pointers of those types? Anyone know?
The plugin API does support exporting managed structs from the plugin, however as far as I know there are not yet any plugins that use this functionality.
On the Plugin API Docs (http://www.adventuregamestudio.co.uk/acplugin.htm), look up the RegisterManagedObject function to get an idea of how it works. In the plugin's script header, you'd need to define the struct like this:
managed struct MyStruct {
Ã, import voidÃ, StructFunc();
Ã, import attribute int StructAttribute;
};
and then use RegisterScriptFunction like this:
RegisterScriptFunction("MyStruct::StructFunc^0", MyStruct_StructFunc);
RegisterScriptFunction("MyStruct::get_StructAttribute", MyStruct_GetStructAttribute);
RegisterScriptFunction("MyStruct::set_StructAttribute", MyStruct_SetStructAttribute);
These functions would then be declared like this:
void MyStruct_StructFunc(MyStruct *ptr) {
// do stuff
}
int MyStruct_GetStructAttribute(MyStruct *ptr) {
// do stuff
return 0;
}
void MyStruct_SetStructAttribute(MyStruct *ptr, int value) {
// do stuff
}
By the way CJ, I made a quick test and it appeared that overriding version of IAGSScriptManagedObject::Dispose isn't invoked when the player exits the game, although the manual states it should.
Here is a test game http://www.geocities.com/scorpiorus82/som_test.zip (copy & paste)
It simulates creating a new object instance at game start. "asserts" then show how it goes further. It calls the method if you restore the game but not if you quit it.
[edit] or should the plugin make necessary actions on engine shutdown and free memory?
In the "myobject.h" header there is "#include <windows.h>" there. Without it I can't use "agsplugin.h" because HWND isn't defined then. Would it be possible to change "agsplugin.h" a bit so that it would define HWND if it's necessary -- currently it only do that for linux and djgpp DOS. Because at present each module has to include "windows.h" before including "agsplugin.h" even if HWND isn't used at all (which is quite a common case).
What I don't understand is the MyStruct pointer that's passed back from the engine.Ã,Â
Quote
void MyStruct_StructFunc(MyStruct *ptr) {
Ã, // do stuff
}
int MyStruct_GetStructAttribute(MyStruct *ptr) {
Ã, // do stuff
Ã, return 0;
}
void MyStruct_SetStructAttribute(MyStruct *ptr, int value) {
Ã, // do stuff
}
What should that class look like in C++?
Quote from: Superman95 on Wed 14/09/2005 00:45:33
What I don't understand is the MyStruct pointer that's passed back from the engine.
It's for passing an address of the object you work with:
MyStruct a;
a.StructFunc();
eventually does...
MyStruct_StructFunc(a);
so that you have an opportunity to perform necessary actions upon the object.
QuoteWhat should that class look like in C++?
In C++ you can define that object in whatever way you like (it can be a C++ class, for instance) and then provide means to access it (ie. set up an interface).
I'll try to come up with an example of what an actual implementation could look like later today. :)
is this allowed?
MyStruct
{
import attribute MyNestedStruct nested;
};
if so, how is that passed in and out of the engine?
I doubt there is a support for returning entire structs, but returning an address should work though:
managed struct MyStruct
{
Ã, Ã, readonly import attribute MyNestedStruct *Ã, nested;
};
The problem is that since it effectively contains a pointer you then need to define them as managed structs and implement appropriate serialize/unserialize functions to make your managed structs support game save/restore mechanism. Unlike with ordinary structs that are saved automatically by the AGS engine.
Quoteif so, how is that passed in and out of the engine?
Plugin should implement this function;
MyNestedStruct *Ã, get_nested(MyStruct *ptr) {
Ã, Ã, return ptr->_Variable_Holding_MyNestedStruct_Address_Here_;
}
then the script can do:
MyStruct *a;
a.nested.xxx = yyy;
I can't think offhand of a reliably working example that would work properly with save/load but wouldn't use managed structs. I'll see if I find something.
You can't actually do that though, because it doesn't allow you to create the pointer in script.
It does allow you to do this...
struct MyStruct
{
import attribute MyNestedStruct* struct;
}
but you can't do this,
MyNestedStruct *foo;
foo = mystruct.struct;
Yes, you can't declare a pointer to an ordinary struct but you can declare one to managed struct.
There is a way to mess with addresses to ordinary structs but that isn't official and rather dangerous in terms of memory access violations.
Quote from: Scorpiorus on Wed 14/09/2005 16:53:21
There is a way to mess with addresses to ordinary structs but that isn't official and rather dangerous in terms of memory access violations.
There is? Damn.
You'll be telling me next there's an unofficial way to call functions recursively.
Or to put structs within structs.
So, if you had two managed structs, one nested in the other, you script header would look like this?
managed struct MyNestedStruct
{
import attribute int X;
import attribute int Y;
// and so on
};
managed struct MyStruct
{
import attribute MyNestedStruct* nestedStruct;
};
Is that right?
Is it also allowed to do this:
managed struct MyStruct
{
import attribute MyNestedStruct* nestedStructs[];
};
Sorry to ask all the questions about this...but it seems pretty darn powerful.
Quote from: SteveMcCrea on Wed 14/09/2005 18:06:29You'll be telling me next there's an unofficial way to call functions recursively.
You mean two functions calling each other? Because one function can call itself no problem:
function blah() { blah(); } ...would kill the stack for sureÃ, :=
QuoteOr to put structs within structs.
Yeah, that I think the main case of the thread.
Quote from: Superman95 on Wed 14/09/2005 19:18:22
So, if you had two managed structs, one nested in the other, you script header would look like this?
Yes, that's correct :)
QuoteIs it also allowed to do this:
managed struct MyStruct
{
import attribute MyNestedStruct* nestedStructs[];
};
Didn't check myself but arrays of properties should work too as far as I know.
Anyway here is a little test I made. It's the game and the plugin.
http://www.geocities.com/scorpiorus82/mychar_v1_0.zip copy & paste (right click/save as maybe also works)
Extract it to AGS main folder (requires AGS v2.71beta5 though)
It introduces two new managed structs TMyCharacter and TStatistics. TMyCharacter has a pointer to TStatistics so you can access character stats in a nested fashion.
Didn't test it much but it seem to work with at least one TMyCharacter object created. Should be no porblem to set up an array of TMyCharacter though.
There is one flaw I know of though: the stats object that within the character isn't controlled by AGS managed objects pool -- I just didn't find a way to increase TStatistics's reference cout through plugin API. Therefore nested TStatistics is created and destroyed at the same time with its TMyCharacter parent. This all means it's impossible to set a pointer to TMyCharacter's TStatistics object through the script:
TMyCharacter *myEgo = TMyCharacter.Create(...);
myEgo.Stats.Health = 10;
TStatistics *theStats = myEgo.Stats; // <----doesn't work (run-time error)
Having created a new TStatistics object should allow such operations with no problems:
TStatistics *stats = TStatistics.Create(...);
TStatistics *theStats = stats ; // <--should work fine
CJ:
I noticed that autocomplete doesn't show static functions whose scriptheader is registered via plugin, thus:
TMyCharacter *myEgo = TMyCharacter
.doesn't show static member function
Create.
Also, if I try to register "import TMyCharacter mycharacter;" it doesn't show list after "mycharacter", dot.
Quote from: Scorpiorus on Wed 14/09/2005 21:29:29
You mean two functions calling each other? Because one function can call itself no problem:
function blah() { blah(); } ...would kill the stack for sure :=
I must be doing something wrong. I tried that and it wouldn't let me. Maybe I just suck at declaring functions.
well, that would kill the stack because there's not there to stop the recursion...
but you should be able to do this.
function foo(int stop)
{
stop++;
Ã, Ã, if(stop < 5)
Ã, Ã, Ã, Ã, foo(stop);
}
This would call itself 5 times and then start unwhinding. Well, actually, 4 times I think.
I know that.
Have you actually tried to make a function call itself? In AGS script, I mean.
Hmm, normal function works fine. Maybe there is an issue with member functions, I haven't tried recursion with them.
Huh.
I had to write the clipping and sorting routines as stacks in Ags3d because I couldn't get recursion to work. Oh well, they are (slightly) faster that way. How exactly do you declare the function? I'd really appreciate seeing the script for your test case.
Cheers!
Well, just...
// main global script
function test(int n) {
if (n > 0) test(n-1);
}
function game_start() {
Ã, test(5);
}
By the way, the script stack size is 4kb if I remember correctly.
QuoteIn the "myobject.h" header there is "#include <windows.h>" there. Without it I can't use "agsplugin.h" because HWND isn't defined then. Would it be possible to change "agsplugin.h" a bit so that it would define HWND if it's necessary -- currently it only do that for linux and djgpp DOS. Because at present each module has to include "windows.h" before including "agsplugin.h" even if HWND isn't used at all (which is quite a common case).
The reason it doesn't check for this is because if I recall correctly, HWND is a typedef rather than a #define in windows.h, so it can't do an #ifdef check for it.
I guess you can always put this in your code before you include agsplugin:
#define HWND int
though it's a bit of a hack, I admit.
QuoteAnyway here is a little test I made. It's the game and the plugin.
Good stuff Scorpiorus, it's good to see that you've had some success with it :)
QuoteThere is one flaw I know of though: the stats object that within the character isn't controlled by AGS managed objects pool -- I just didn't find a way to increase TStatistics's reference cout through plugin API. Therefore nested TStatistics is created and destroyed at the same time with its TMyCharacter parent.
You can just register it with RegisterManagedObject at the same time as you register the parent.
There is no way to manually adjust the refernece counts but this shouldn't matter since it will be created with a count of 0, and its Dispose method can refuse to discard it until its parent has been destroyed.
QuoteI noticed that autocomplete doesn't show static functions whose scriptheader is registered via plugin, thus:
TMyCharacter *myEgo = TMyCharacter.
doesn't show static member function Create.
I noticed this too, I'll look into it.
(Edit by strazer: AGS v2.71 Beta 6: * Fixed static methods in plugin headers not being picked up by autocomplete.)
QuoteI had to write the clipping and sorting routines as stacks in Ags3d because I couldn't get recursion to work.
AGS does have a limit on the depth of recursion -- the call stack cannot go more than 50 calls deep so if you were attempting something complicated it may cause an error.
Quote from: Pumaman on Thu 15/09/2005 22:11:18The reason it doesn't check for this is because if I recall correctly, HWND is a typedef rather than a #define in windows.h, so it can't do an #ifdef check for it.
I guess you can always put this in your code before you include agsplugin:
#define HWND int
though it's a bit of a hack, I admit.
Yep, something similar I did in the past. I just thought maybe "agsplugin.h" could #ifdef-check for WINVER or maybe for _WINDEF_ (windef.h) to see if HWND is already declared but it still has some pitfalls like including "windows.h" after "agsplugin.h" when HWND is already defined in "agsplugin.h". So probably, it is not worth bothering with it at the moment.
QuoteGood stuff Scorpiorus, it's good to see that you've had some success with it
It was a pleasure to try things out and convince myself how powerful the mechanism is. :)
QuoteYou can just register it with RegisterManagedObject at the same time as you register the parent.
There is no way to manually adjust the refernece counts but this shouldn't matter since it will be created with a count of 0, and its Dispose method can refuse to discard it until its parent has been destroyed.
Ah, thanks for the tip! I completely forgot that I can make AGS refuse discarding an object from its pool by returning 0 from Dispose. I overlooked it because probably had my mind focused on the conception that each managed object ref count must be > 0 if it's being referenced by something (no matter what it is: a script pointer or a plugin pointer). So I thought if I registered an object it would be discarded almost instantly due to the fact that it's not picked up by a script pointer, and didn't even think that I can actually control that discarding. Now I understand why one may want to leave object drifting in the pool even if its reference count is zero and why therefore the
force parameter passed to Dispose is so important (eg: when we are about to quit the game and child object's Dispose method is called earlier than its parent one -- we must release child object because its parent is about (but hasn't been yet) to be released too). :)
I haven't yet tried implemeting this but there seem to be one issue: the child object then needs to know if its parent is gone and thus therefore could set up some sort of a soft (unmanaged by ags) back reference. But it somewhat contradicts the idea that an object is a self-contained entity because in that case an object is forced to have an Owner field (what may also be deliberate though). Inconvenience arises when one object can be referenced by several parent objects ([edit]of different types[/edit]), then it must have as many Owner fields as many parent object are there -- so each time I'm going to add a pointer to object field in my parent object I must ensure this child object can set up a back reference and that results in having to mess with child object's code.
Of course, the simplest approach would be to introduce plugin's internal reference count mechanism to avoid all these back references but is it really necessary and could the plugin use AGS pool services to add/release ref counts instead? I know however there is then a chance to accidently set up a circular reference, so what would the managed objects pool do in that case, would it still call Dispose methods with force set to 1 if we are about to quit the game (even if object's ref count is not zero)?
Oh, one more thing I forgot about: woud it be possible to get managed object address by its ID (I have a guess it's that
key parameter) and the other way round?
Unless I didn't understand something there is currently no means in plugin API to link parent and child object just after their de-serialization. If I could store child objects IDs during parent object serialization then it were possible to retrieve them back on de-serializing and re-initializate parent object pointers.
Sorry for a long post, but the main goal I'm after is to let AGS managed object pool control plugin-specific objects on its own.
What do you think CJ?
QuoteYep, something similar I did in the past. I just thought maybe "agsplugin.h" could #ifdef-check for WINVER or maybe for _WINDEF_ (windef.h) to see if HWND is already declared
Yeah that's not a bad idea, I'll do that.
QuoteI haven't yet tried implemeting this but there seem to be one issue: the child object then needs to know if its parent is gone and thus therefore could set up some sort of a soft (unmanaged by ags) back reference.
Not necessarily -- as long as the parent keeps a reference to the child, it could call some sort of ParentIsDeleted() method on the child at the appropriate time, and the child could set a flag inside itself to remember this.
I'd rather not add functions to modify the reference count to the plugin API -- at the moment referencing counting is handled completely automatically by the script engine and it would seem to be inviting trouble to allow it to be manually adjusted.
QuoteOh, one more thing I forgot about: woud it be possible to get managed object address by its ID (I have a guess it's that key parameter) and the other way round?
That's a good point, it's difficult to re-link a parent and child object after de-serializing them at the moment, so I'll add some methods to help with that.
Quote from: Pumaman on Fri 16/09/2005 18:58:20Not necessarily -- as long as the parent keeps a reference to the child, it could call some sort of ParentIsDeleted() method on the child at the appropriate time, and the child could set a flag inside itself to remember this.
Yeah, that's what I meant with "to introduce plugin's internal reference count mechanism to avoid all these back references" I should have made myself clear. The object's
flag effectively becomes a reference count and ParentIsDeleted would then decrease it acting like ReleaseRef. If I'll implement this functionality in a base class I can then use it for any other object derived from base. It just appeared to me like mirroring of what can already be done by the ags managed object pool itself that's why I wondered about possibility to expose ags functionality but I appreciate that it can cause nasty troubles and that the engine shouldn't really rely on a badly written plugin. :)
One more question if I may ask: how should I register static member functions?
Struct::StaticFunc(int a, int b, int c);
like this?
"Struct::StaticFunc^3"
or
"Struct::StaticFunc"
...because for me both of them seem to work.
It doesn't matter whether you include the ^3 or not.
The advantage of using it is that it allows you the scope of adding extra (optional) parameters to the method in future, which then allows your plugin to expose two different versions of the function for backwards compatibility.
ie. you can expose both of these:
"Struct::StaticFunc^3"
"Struct::StaticFunc^4"
but not both of these:
"Struct::StaticFunc^3"
"Struct::StaticFunc"
That's great! Thanks for clarifying!
Talking about method overloading...can you do that in script now?
for instance:
foo(int one);
foo(int one, int two);
or is this just strickly available in the plugin. Also, different types don't count as an overload...correct?
for instance:
foo(int one);
foo(string one);
and on a related topic...is it possible to do overrides?
for instance
struct base
{
void foo(int one);
};
struct mystruct extends base
{
void foo(int one);
}
so that mystruct.foo hides base.foo?
I'm pretty sure that function overloading is defined as two or more functions with the same name, but with different return types/parameter lists.
No, method overloading is not supported, and neither is polymorphism (an inherited class overriding a base class function).
QuoteOh, one more thing I forgot about: woud it be possible to get managed object address by its ID (I have a guess it's that key parameter) and the other way round?
Functions to do this have been added to 2.71 beta 6. Also, in beta 6 the RegisterMAnagedObject function returns the key so that you can save it somewhere at that point if you want to.
Quote from: Pumaman on Sun 18/09/2005 10:48:08Also, in beta 6 the RegisterMAnagedObject function returns the key so that you can save it somewhere at that point if you want to.
Ah, thanks for a note I didn't notice that bit -- quite handy!