plugins and managed types

Started by Superman95, Tue 13/09/2005 03:15:01

Previous topic - Next topic

Superman95

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?

Pumaman

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, 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
}

Scorpiorus

#2
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).

Superman95

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

Scorpiorus

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

Superman95

is this allowed?

MyStruct
{
    import attribute MyNestedStruct nested;
};

if so, how is that passed in and out of the engine?

Scorpiorus

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

Superman95

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;

Scorpiorus

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.

Kweepa

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.
Still waiting for Purity of the Surf II

Superman95

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.

Scorpiorus

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.

Kweepa

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.
Still waiting for Purity of the Surf II

Superman95

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.

Kweepa

I know that.
Have you actually tried to make a function call itself? In AGS script, I mean.
Still waiting for Purity of the Surf II

Scorpiorus

Hmm, normal function works fine. Maybe there is an issue with member functions, I haven't tried recursion with them.

Kweepa

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!
Still waiting for Purity of the Surf II

Scorpiorus

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.

Pumaman

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

Scorpiorus

#19
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?

SMF spam blocked by CleanTalk