Adventure Game Studio

AGS Support => Modules, Plugins & Tools => Topic started by: monkey0506 on Sat 21/03/2009 01:10:47

Title: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Sat 21/03/2009 01:10:47
Now presenting the Stack module, which introduces vectorized, generic stacks for all your data storage needs!

stack: Works very similarly to the way an array works. Stores a set of data within a single variable.

vectorized: Means that the size of the stacks does not need to be predefined anywhere. They will grow/shrink as needed. Great for memory management and dynamic scripting.

generic: Means that the stacks do not conform to any one specific data type. You can push any/all of the following types onto a stack:
The included documentation includes a full function list and some example scripts. What are you waiting for? Download it now, fool (https://drive.google.com/file/d/0B13dWpIY47QpZ1BaZlRDVTRaVjg/view?usp=sharing)!

Requires AGS 3.4.0.3 (Alpha) or later

ALL VERSIONS of this module are now available on Github (https://github.com/monkey0506/ags-module-stack/releases).

Upon request, other data types (such as Hotspot, Object, etc.) could be made available. The requested data must be able to be interpreted as one of the basic data types (int, float, or String) however or the request will be rejected (i.e., File, Overlay, etc. would be out). Anything requiring a persisting pointer would also be rejected (DynamicSprite, Overlay, etc.).

25 January 2015:

The Stack module v2.0 is out! Get it while you can!

21 August 2009:

Uploaded v1.3 which is more bug fixing. Specifically String.Format has a limit on the length of the returned String, so where applicable I've replaced it with instances of String.Append (which has no limit). Further I modified the way that stack objects get pushed in to make it more secure. Previously if you pushed the same stack copy onto a stack multiple times it could potentially corrupt the internal data structure. This version completely resolves that possibility.

29 March 2009:

Uploaded v1.2a which is just a quick bug fix. Stack::GetItemsArray would request a zero-sized array which AGS doesn't like for some reason so I just put a quick check to prevent that. I also retro-conned the information below which suggested it was written today. It wasn't. :=

28 March 2009:

Uploaded v1.2 of the module which includes:

- The new functions Stack.GetItemsArray, File.WriteStack, File.ReadStackBack, and Stack.LoadFromFile.
- Improved formatting of Stack::Copy to further prevent fatal error-inducing collisions.
- Bug fix for Stack.Push where if you were specifying an index the ItemCount was still getting increased.
- Adds the data type eStackDataInvalid to denote when the data stored is not valid StackData.
Title: Re: MODULE: Stack v1.0 - Vectorized generic stacks, oh my!
Post by: Trent R on Sun 22/03/2009 08:03:28
Monkey, you're a bloody genius. Just the other day I was thinking how nice it would be to have a String queue, and now I have it. Started skimming through the manual and found that it supported FIFO. Awesome work.


~Trent
Title: Re: MODULE: Stack v1.0 - Vectorized generic stacks, oh my!
Post by: monkey0506 on Sun 22/03/2009 15:54:07
Of course it supports FIFO, what sort of madman do you take me for? := It also supports FILO and Random, and also you can specify an exact index as well. All Stacks default to FILO.

Glad to hear it's working well for you Trent.

This started off as just a way of killing time at work. I opened up a Wordpad and whenever I had downtime I just started typing. Being the complete nerd that I am, I was very strict to conforming to valid code although I had no way of testing it, and what's more, no way of saving the document (as per my employer's confidentiality policies regarding our customer's information). Eventually I started realizing that this was actually a viable idea for a module and brought it (the idea) home with me. Hours upon hours of debugging later, and here we are! 8)
Title: Re: MODULE: Stack v1.0 - Vectorized generic stacks, oh my!
Post by: Dualnames on Thu 26/03/2009 15:48:15
You're BAD!!In a good way. :D :D :D
Title: Re: MODULE: Stack v1.0 - Vectorized generic stacks, oh my!
Post by: monkey0506 on Fri 27/03/2009 01:10:05
Glad to hear you like it!

Edit: Uploaded v1.2 of the module. In complete defiance of the scientific method, I was changing several things at once which was causing the problems. Starting back at v1.0 and taking one thing at a time allowed me to successfully debug the code. Funny the way that works. :P
Title: Re: MODULE: Stack v1.2 - Vectorized generic stacks - Updated 29 March 2009
Post by: Dualnames on Sat 28/03/2009 21:45:48
It's really weird to add that I just passed a subject on my university dealing with stacks.. on microprocessors..well, good job there again.
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: Crimson Wizard on Fri 18/12/2009 01:07:04
I tried to use it in the AGS 3.2 RC project and it gave me a compiler error:

Stack.asc(50): Error (line 50): undefined symbol 'inventory'

BTW, do I understand correctly that it allows to push different types in one stack? Because I would like to use it to store custom structs and I'll have to push all the struct data in there, which consists of different types of variables.


EDIT: Yearg, heh, funny. It looks like I have to create at least 1 inventory item and 1 gui item (and maybe somethng else) for those inventory/gui arrays to work in scripts.
Looks rather like AGS bug.
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: monkey0506 on Fri 18/12/2009 04:36:20
Well I've actually brought that up to Chris before, but I don't specifically recall whether he said he considered it a bug or not. Basically, as you said, the inventory and gui arrays don't exist at all unless there is at least 1 member of their respective types available. The same is also true of the dialog array. I think the rest are safe...you must have at least one character and the hotspot, region, and object arrays are all statically sized.

To answer your question, yes, a single stack can contain data of multiple types. As I revealed (in the spoiler text) above, the StackData type is really just a String, but I'm also storing additional information about the data and such so it's not intended for use as a raw string value.

If you take a look at the Stack Extension Guide (http://meleepta.x10hosting.com/Stack%20Extension%20Guide.txt) there's information on how you can create functions to interoperate directly with the built-in functions. To briefly touch on how to do this, I'll copy the example from said guide here:

// Stack.ash
enum StackDataType {
  // all of our other types
  eStackDataCharacterStat = 'x' // we must add a new key to our enum to extend it to a new type
};

// CharacterStats.ash (just an example)
struct CharacterStats { // an example struct
  int HP;
  int MP;
  int XP;
  import StackData ToStackData(); // our custom conversion function
  import bool LoadFromStackData(StackData data); // custom loading function
};

// CharacterStats.asc
import StackDataFormat; // grab this from Stack.asc (it is exported, but not globally imported)
// we need to use this for our data conversion

StackData CharacterStats::ToStackData() {
  StackData type = StackData.Format(StackDataFormat, eStackDataCharacterStat);
  return StackData.Format("%s%d,%d,%d", type, this.HP, this.MP, this.XP);
}

bool CharacterStats::LoadFromStackData(StackData data) {
  if ((data == null) || (data.GetType() != eStackDataCharacterStat)) return false;
  data = data.Substring(StackDataFormat.Length - 1, data.Length);
  String buffer = data.Substring(0, data.IndexOf(","));
  this.HP = buffer.AsInt;
  data = data.Substring(buffer.Length + 1, data.Length);
  buffer = data.Substring(0, data.IndexOf(","));
  this.MP = buffer.AsInt;
  data = data.Substring(buffer.Length + 1, data.Length);
  this.XP = data.AsInt;
  return true;
}

// example usage
CharacterStats csEgo;
csEgo.HP = 100;
csEgo.MP = 15;
csEgo.XP = 0;

// serialize the data into a stack
Stack s;
s.Push(csEgo.ToStackData());

// unload the data back from a stack
csEgo.LoadFromStackData(s.Pop());


Another way of doing it would be to simply push/pop each individual property, but as you can see scripting up a couple of quick conversion functions makes it much easier to push/pop custom structs. :=

Hope this helps. Be sure to let me know if you have any further questions or comments.
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: Crimson Wizard on Sat 19/12/2009 01:01:21
Quote from: monkey_05_06 on Fri 18/12/2009 04:36:20
Another way of doing it would be to simply push/pop each individual property, but as you can see scripting up a couple of quick conversion functions makes it much easier to push/pop custom structs. :=
Yes, actually this was really useful tip!

Couple of notes though

Quote from: monkey_05_06 on Fri 18/12/2009 04:36:20
// Stack.ash
enum StackDataType {
  // all of our other types
  eStackDataCharacterStat = 'x' // we must add a new key to our enum to extend it to a new type
};


I guess it isn't actually important to edit Stack.ash, you can just put this new definition in your own script either in a new enum or as #define


Quote from: monkey_05_06 on Fri 18/12/2009 04:36:20

// CharacterStats.asc
import StackDataFormat; // grab this from Stack.asc (it is exported, but not globally imported)
// we need to use this for our data conversion

It should be import String StackDataFormat, otherwise error is given.
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: monkey0506 on Sat 19/12/2009 02:12:03
You're right that it's not strictly necessary for the enumerated value to be contained in Stack.ash, but I strongly recommend that you at least look to see what values are there. The module expects each data type to have its own unique identifier which is interpreted as a char. If two data types have the same identifier defined there is no way to distinguish one from the other which could result in invalid conversions, potentially a fatal operation when working with custom struct types.

And your right, I forgot to include the type in that import statement. Sorry about that.

One further note, the format of the StackData you work with is actually very lenient. The only requirement is that you do something such as:

  StackData type = StackData.Format(StackDataFormat, UNIQUE_TYPE_IDENTIFIER_CHAR_VALUE);
  return StackData.Format("%sDATA", type);


Where UNIQUE_TYPE_IDENTIFIER_CHAR_VALUE of course would generally be the enumerated value assigned to the data type you are working with. The DATA portion can be (string) formatted however you want. The only (string) values reserved by the module are:
"**STACKDATA**"
"**STACKDATA:"
"**STACKDATA STACK:"
"**STACKDATA NULL STRING**"
So as long as you don't have anything like that, feel free to store the data however you'd like. If you need to store a non-basic type, just check out the module code to see how I processed the conversion. I've actually been working on a method of serializing the more complex types (such as DynamicSprite and Overlay) but for now there's no way of storing anything that requires a persistent pointer like that. ;)

In any case, thanks for the feedback. It's nice to know somebody finds this stuff useful. :)
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: Crimson Wizard on Sun 20/06/2010 08:49:10
Monkey, I was actually trying to understand internal module algorythms, and found them way too complex. I can be wrong, ofcourse, or missing something; but I usually prefer most simple way to do things (which are not always perfectly safe, perhaps), and your code there makes me shudder :). I have already tested this Stack in my "Shooting Gallery" tech demo, and so far it never slowed it down, even though game required reading/writing Stack elements every single tick. So, it's not that module works slow. It's just that I don't understand why it should be so complex.

My main doubt is related to the method of spawning a dynamic array and populating it with elements from Data String every time Stack needs to read or write an element. Couldn't you just find a corresponding substring from the Data String directly and use it?
I tried to write my own "vector" module with "lighter" implementation (as mentioned above), and it seem to work nicely... what do I miss? if anything?

PS. BTW, I wrote 2 "classes" for user-friendly reading and writing of custom structs from/into String. I wonder, if you can find it a possibly useful contribution to your module?
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: Monsieur OUXX on Sun 20/06/2010 13:27:20
Quote from: Crimson Wizard on Sun 20/06/2010 08:49:10
I tried to write my own "vector" module with "lighter" implementation (as mentioned above), and it seem to work nicely... what do I miss? if anything?

PS. BTW, I wrote 2 "classes" for user-friendly reading and writing of custom structs from/into String. I wonder, if you can find it a possibly useful contribution to your module?

Post what you have! :-)

PS: don't expect answers from Monkey, he just DOESN'T HAVE the Internet.  :)
Title: Re: MODULE: Stack v1.3 - Vectorized generic stacks - Updated 21 August 2009
Post by: monkey0506 on Fri 06/08/2010 22:32:49
Hey CW, just saw your post here. It's true that the Stack module isn't highly optimized. The reasoning behind popping the entire array every time was actually a simple matter of convenience for me. Seeing as items can be added/removed dynamically from any index, storing static indices in the items wouldn't work. So the alternative would be to use an implementation of my IndexOfNth function (see StringPlus module). That would definitely create less overhead.

As I said, it was never optimized though. So yes, it could be made better, but my primary point of focus was functionality vs. speed and doing it the way I did was easier at the time. ;D

Edit: In my work with the List module I've actually managed to get Push/Pop working pretty nicely with my StringPlus module's IndexOfNth function (local copy provided if the module is not included). I'm doing some more work with that first, but I plan to release an updated version of this module as well. Also, there was already some new functionality that I was planning on incorporating but for some reason never got around to releasing.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Sun 25/01/2015 10:37:43
As part of my on-going effort to definitively prove that there is nothing that I won't code when it comes to AGS, I made a new version of this module! ;-D And then, I decided it was crap, so I rewrote it again to be 1000% better than the aforementioned version which is now lost forever to the sands of time!

The biggest news is that this version of the module is FAST* because I can use dynamic arrays in structs now. The current limitations on managed structs led me to some suboptimal code when working with Strings, but I can pretty consistently push one million ints (the current max size for a dynamic array btw) into a Stack in a single for loop in only about 30 seconds! By way of comparison, with v1.3 I tried pushing a measly ten thousand ints into a Stack and the program froze for more than half an hour before I force-closed it with Task Manager. I can push ten thousand ints with this version in under a second.

To really optimize the speed, I made it possible to set the capacity of a Stack (setting the size of the underlying dynamic array) and avoid additional copying. Considering how much unnecessary copying was taking place in v1.3 of this module, anyone who actually used the older module should be quite happy with the improvements I've made.

Anyway, download link (https://drive.google.com/file/d/0B13dWpIY47QpZ1BaZlRDVTRaVjg/view?usp=sharing).

*To be fair, it's not native code "fast", but I don't think many people are going to be trying to push hundreds of thousands of items at once in a single frame of their game...
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Dualnames on Mon 26/01/2015 08:38:53
So, wait, i wanna ask something, would processing the stack be faster than currently processing a huge array?
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Crimson Wizard on Mon 26/01/2015 09:00:23
Quote from: Dualnames on Mon 26/01/2015 08:38:53
So, wait, i wanna ask something, would processing the stack be faster than currently processing a huge array?
Of course not.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Mon 26/01/2015 09:25:17
It wouldn't be faster than processing an array because all that the module is doing is processing arrays. :P

However, if you want a type-generic container class that automatically grows as needed, then this still fits that description (which is what it is designed for).
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Dualnames on Mon 26/01/2015 11:34:55
Damn, I thought you somehow broke a thing and made a dream come true. Oh well.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: ollj on Wed 25/11/2015 18:29:31
After coding ags script for 6 months i finally understand how this module does its things (since managed structs and attributes in ags are a bit tricky), and its a pretty good example for advanced ags scripting.

But why did ypu make each StackData instance store a float, even if it doesnt store any significant float, that seems so inefficient for the sake of speed and flexibility.

floats are just too different from the other datatypes, that can be more easily enumerated with an int, used as pointer.

You might aswell make a float buffer array to point at, just like you made a string buffer array.

the .AsString property could also return Character.Name; Object.Name; String.Format("&d",i); String.Format("&f",f);
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Mon 07/12/2015 08:10:38
Pooling floats seems very much the worse option in this case. Attempting to keep a unique pool would require using some arbitrary epsilon and a lot of floating-point comparisons. Not keeping a unique pool would require adding an item to the pool every time a float is converted into StackData -- and note that AGS lacks destructors! The pool would never be able to release an item without an explicit function call placed by the user. Memory conservation would not even be guaranteed by pooling the floats, as if the total number of floats converted into StackData exceeded the number of StackData objects currently in existence (despite the lack of destructors, the objects are still destroyed if they go out of scope with no references to them), then there would effectively be a memory leak (memory reserved that is never again referenced by the program, which is never freed until the program terminates). No, I don't think that this is a better solution. Perhaps I could mangle floats and store them in some combination of StackData.Type, StackData._idata, and StackData._scacheID. The question, then, I think, would be whether it's worth conserving 4 bytes of memory (per StackData instance) to have to reconstruct floating-point values... something like:

Code (ags) Select
return (IntToFloat(this._idata) + (IntToFloat(this._scacheID) / 1000000));

Floating-point division every time the value is retrieved? Ugh!

I still just don't think it would be worth reclaiming 4 bytes of memory per StackData object (1 KB per 256 StackData objects). You would have to have well over 200K StackData objects at any given time for that to amount to a single MB of "lost" memory. You could have a million StackData objects and have "lost" less than 4 MB of available RAM. This is hugely insignificant.


As for the suggestion to use the AsString property as a sort of "ToString" conversion method, I actually like the idea. I could easily see reimplementing the "StrictTypes" setting to allow that behavior to be toggled.

Thank you for your feedback. While I don't agree on the float issue, it is appreciated! :=
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Monsieur OUXX on Mon 18/01/2016 16:39:07
I'd like to devise a simple way to make this module as easy as possible to adapt to any custom struct.
(Note 1: by "any custom struct" I'm not talking about the ones you black-flagged, i.e. the ones that have a temporary life, like File* and such)
(Note 2: I'm not talking about the universal stack "Stack", which aims at storing all types at once, but more something like StringCache_t, which aims at storing one specific type at a time. Only then, why not, include that manually in "Stack" if necessary, to make it even more universal)

For example:
- In the module, I'd put a #define CUSTOMTYPE AnyCustomType
- Everywhere in the module, you (Monkey) only always use CUSTOMTYPE as argument to functions, or return value, etc.
- Then the end-user can import this script as many times as he wants (under a different name each time, e.g. MyStruct1Stack, MyStruct2Stack, etc.)  ...
- ...And in each import, he only changes the define line to #define CUSTOMTYPE MyStruct1, or #define CUSTOMTYPE MyStruct2, etc.
(I think this would be possible only thanks to the newer #define that can overwrite the previous definition each time you redefine it -- or am I mistaken?)



Any dirty trick like the one I described above is welcome, the goal is simply to be able to implement as many custom stacks as possible with as little human intervention as possible.
What do you think? Simpliicty is the master word here.



===========

also, an unrelated question: why did you define Grow and set_Capacity as extenders?
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Tue 19/01/2016 13:28:02
I've deleted a rather large post that I had typed up, so please bear with me on this, but in order to be stored into a Stack instance, you have to have some way of referring back to the individual instances of your custom struct type. There are two ways of doing this:

Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Monsieur OUXX on Tue 19/01/2016 14:24:05
I still can't get my head around the AGs language restrictions.
Let me make sure I understand: if my custom struct contains, let's say, a GUI*, then I'm screwed? Because:
- if I go down the "managed struct" path, then it's forbidden to use pointers,
- and if I go down the stringify road, then I cannot stringify a GUI*.
Or can I? I believe the whole purpose of your Stack struct is to not stringify the built-in types, like GUI*.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Crimson Wizard on Tue 19/01/2016 14:35:35
Quote from: Monsieur OUXX on Tue 19/01/2016 14:24:05
- and if I go down the stringify road, then I cannot stringify a GUI*.
Or can I? I believe the whole purpose of your Stack struct is to not stringify the built-in types, like GUI*.
You could use GUI's ID number, I believe.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Tue 19/01/2016 15:30:44
Yes, most things can be stringified in some meaningful way. If you have to store a GUI*, then you could stringify it by using the GUI.ID. If you don't mind having a separate script for your struct definition though, you could do this:

Code (ags) Select
// header

autoptr managed struct Thing
{
  import attribute GUI *TheGUI; // attributes are still okay, because they're functions, not actual pointers!
  protected int guiID; // int defaults to 0, so this will be GUI.ID + 1 instead -- 0 will be null GUI*
  // blah
};

import bool PushThing(this Stack*, Thing thing, int index=SCR_NO_VALUE, bool insert=true);
import Thing PopThing(this Stack*, int index=SCR_NO_VALUE, bool remove=true);


Code (ags) Select
// script

GUI* get_TheGUI(this Thing*)
{
  if (this.guiID == 0)
  {
    return null;
  }
  return gui[this.guiID - 1];
}

void set_TheGUI(this Thing*, GUI *value)
{
  if (value == null)
  {
    this.guiID = 0;
  }
  else
  {
    this.guiID = value.ID + 1;
  }
}

bool PushThing(this Stack*, Thing thing, int index, bool insert)
{
  this.PushGUI(thing.get_TheGUI(), index, insert); // inside THIS SCRIPT ONLY, we must use the accessor functions directly
  // ...
  return true; // TODO: return correct result based on whether all members were pushed
}

Thing PopThing(this Stack*, int index, bool remove)
{
  Thing thing = new Thing;
  thing.set_TheGUI(this.PopGUI(index, remove));
  // ...
  return thing;
}


Code (ags) Select
// some other script

Thing myThing = new Thing;
myThing.TheGUI = gMyThingGUI;
Stack stack;
stack.PushThing(myThing);
// ...
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Monsieur OUXX on Tue 19/01/2016 15:42:08
OK thanks a lot to both of you.
Monkey, would you mind post in that other thread from yesterday where I ask the meaning of autoptr? Thanks.

EDIT: Oh, you already did.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Monsieur OUXX on Mon 13/02/2017 13:07:49
Warning: the module does not compile in AGS 3.4 (patch 2), because the array "inventory" does not exist anymore.
I haven't found a workaround yet.
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: Crimson Wizard on Mon 13/02/2017 13:54:46
Quote from: Monsieur OUXX on Mon 13/02/2017 13:07:49
Warning: the module does not compile in AGS 3.4 (patch 2), because the array "inventory" does not exist anymore.
I haven't found a workaround yet.

You must create at least 1 inventory item to make it appear. This does not relate to particular version of AGS.

We have an issue in the tracker:
http://www.adventuregamestudio.co.uk/forums/index.php?issue=683.0
Title: Re: MODULE: Stack v2.0 - Vectorized generic stacks - Updated 25 January 2015
Post by: monkey0506 on Fri 30/06/2017 16:01:53
I'm proud to announce that all versions of this module are now available on Github:

https://github.com/monkey0506/ags-module-stack/releases

Version history between minor versions is now easier to visualize, and new releases are automatically built courtesy of Travis CI. 8-)