Static Properties revisited! - Workaround present[ed]

Started by monkey0506, Wed 09/05/2007 17:45:03

Previous topic - Next topic

monkey0506

Hooray...in all the spare time I have working...50...55...64 hours a week (the last two weeks' and this week's schedules (respectively))...I've managed to discover another mostly useless workaround to a major flaw in AGS. :P

It's very hackish, no guarantee that it would work in future versions, stupidly complex to actually implement for the trouble it's worth (i.e., I'm not sure the benefit outweighs the hassle of implementation)...etc., etc.

But all that aside, I've discovered a workaround to custom struct[ure]s having static properties. The basic setup goes something like this:

Code: ags
// script header

struct MyStruct {
  import static attribute int MyStaticInt; // $AUTOCOMPLETESTATICONLY$
  protected import int get_MyStaticInt(); // $AUTOCOMPLETEIGNORE$
  protected import void set_MyStaticInt(int value); // $AUTOCOMPLETEIGNORE$
  };

// main script

int MyStruct_MyStaticInt;

protected int MyStruct::get_MyStaticInt() {
  return MyStruct_MyStaticInt;
  }

protected void MyStruct::set_MyStaticInt(int value) {
  MyStruct_MyStaticInt = value;
  }


The way AGS works with static properties, you must import an attribute into the struct. So the generic formula for importing the property is something like this:

Code: ags
import static attribute TYPE NAME;


Where TYPE is the data type of the variable you want to create (i.e., int, float, String, etc.) and NAME is the name you want to give it. Adding the comment "$AUTOCOMPLETESTATICONLY$" will tell the script autocomplete to only autocomplete the property if it is being accessed statically.

You must then import get/set functions for the property like this:

Code: ags
import TYPE get_NAME();
import void set_NAME(TYPE value);


You can optionally make them protected, however if you do this you can't access these function from within any static functions of your struct (if you will need to access the static property from within your static functions, it is advised you leave the "protected" keyword off). If you don't need access to them within static functions, it will help to hide/protect the functions from the users, as they don't really need to use the get/set functions, they have direct access to the static property. Another method of security you can put in place (though it won't actually prevent the user from calling these functions, it will hide them from the autocomplete) is to put the comment "$AUTOCOMPLETEIGNORE$" at the end of each of these lines. This will make the autocomplete ignore these functions.

Next, in the main script, you must define a local variable that will be used internally for storing the value of the static property. Basically the static property will just serve as a pointer to this variable; if this variable changes, so will the static property. So, in the main script put:

Code: ags
TYPE STRUCTNAME_NAME;


You'll also need to define the get/set functions for our static property which due to the way this hack works is simply going to access our local variable:

Code: ags
TYPE STRUCTNAME::get_NAME() {
  return STRUCTNAME_NAME;
  }

void STRUCTNAME::set_NAME(TYPE value) {
  STRUCTNAME_NAME = value;
  }


So...maybe it would just be easier to implement get/set functions for the user to actually use. Or maybe it would be easier to import the variable outside of the struct. Who knows? Goodness knows I don't. But I do know how to make it work as a static variable. 1+ up for me. I think. Maybe. I need sleep.

[EDIT:]

I forgot to mention...if you want to access the static property from within other member functions, you must use the get/set functions OR access the local variable directly.

i.e.:

Code: ags
// script header

struct MyStruct {
  import static attribute int MyStaticInt;
  // blah
  import void RandomizeStaticInt(int min, int max);
  };

// main script

// blah

void MyStruct::RandomizeStaticInt(int min, int max) {
  // this.MyStaticInt = Random(max - min) + min; // THIS WILL CRASH
  // MyStruct.MyStaticInt = Random(max - min) + min; // THIS WILL ALSO CRASH
  // this.set_MyStaticInt(Random(max - min) + min); // doesn't crash
  // MyStruct_MyStaticInt = Random(max - min) + min; // doesn't crash
  }


You can access the static property anywhere you like so long as the structure has been defined (:P) and you're not trying to access it from within a member function of the structure.

Failure to comply with the access procedures will result in a run-time error such as (from AGS 2.8 alpha 6):

Quote---------------------------
Adventure Game Studio
---------------------------
An internal error has occurred. Please note down the following information.
If the problem persists, contact Chris Jones.
(ACI version 2.80.923)

Error: is_script_import: NULL pointer passed

---------------------------
OK   
---------------------------

Pumaman

Obviously I can't recommend that anyone uses this method, it's completely unsupported and is not guaranteed to work at all.

But nice find anyway ;)

GarageGothic

Being clueless about static, void, protected and all those lovely, undocumented functions that AGS just assumes you know from other programming languages, could you please explain what kind of things this hack could be used for?

SSH

Well, at the moment a struct can have static (i.e. general, not instance-specific) functions, but not static variables... Monkey just showed a way to make it LOOK like it has static variables (although actually, you're just providing a static struct interface to a variable that is declared somewhere else)

I wonder if MyStruct.MyStaticInt++ works...

12

GarageGothic

Thanks for the explanation SSH. I'm not sure I understand it fully, but it doesn't sound like anything I will need to use anyway.

Slightly off-topic, is there any chance that one of you scripting wizards might write a brief introduction to these undocumented commands and their uses? (or if one exists, please direct me to it). I've previously studied some books on other programming languages (Java and C#, I think) in an attempt to grasp it, but their example scripts were so different from AGS script that it wasn't very helpful.

monkey0506

Using this same basic concept I've also worked out vector (dynamically-sized array) properties. Total properties/functions for implementation of a vector property: 5. Storage of vector data in a protected String property, geti/seti functions, and an integer to store the size of the array (and of course the vector property itself). It, of course, isn't as simple as if we had proper implementation of vector properties, but it is usable:

Code: ags
struct IntVector {
  import attribute int Ints[];
  protected import int geti_Ints(int index); // note this is "geti_Ints" not "get_Ints" and it requires a single parameter for the index passed to the array
  protected import void seti_Ints(int index, int value);
  writeprotected int IntCount;
  protected String __ints;
  };

protected int IntVector::geti_Ints(int index) {
  if ((index < 0) || (index >= this.IntCount)) return 0;
  String buffer = this.__ints;
  int i = 0;
  while (i < index) {
    buffer = buffer.Substring(buffer.Contains(";") + 1, buffer.Length);
    i++;
    }
  buffer = buffer.Truncate(buffer.Contains(";"));
  return buffer.AsInt;
  }

protected void IntVector::seti_Ints(int index, int value) {
  if ((index < 0) || (index > this.IntCount)) return; // provides the ability to grow the vector by 1 by passing in its current size as the index
  if (this.__ints == null) this.__ints = "";
  if (index == this.IntCount) {
    this.__ints = this.__ints.Append(String.Format("%d;", value));
    this.IntCount++;
    return;
    }
  String buffer = this.__ints;
  int i = 0;
  while (i < index) {
    buffer = buffer.Substring(buffer.Contains(";") + 1, buffer.Length);
    i++;
    }
  this.__ints = String.Format("%s%d%s", this.__ints.Substring(0, this.__ints.Contains(buffer)), value, buffer.Substring(buffer.Contains(";"), buffer.Length));
  }


This would be more useful, for example, to properly implement a vector property into a struct you needed a vector for than for writing a vector data type (class/struct). I simply did this more as a proof of concept than anything else, though it prove useful. There is still the limitation of not being able to access the property within member functions.

But just for the record:

Code: ags
MyStruct.MyStaticInt++;


As well as

Code: ags
IntVector ivec;
ivec.Ints[0]++;


Both work as expected.

Just as a side note, while pondering what the hell is even spawning such insane ideas inside my skull, I've remembered something...this is similar to the way AGS's built-in static and vector properties (an example of a built-in vector being String.Chars) are defined. I'm not sure how the get/set functions are internally handled, but all of the built-in properties are defined as attributes which are imported into the struct. The original idea was simply to try and figure out how this "import attribute" stuff worked. From there it was a logical progression to "providing a static struct interface to a variable that is declared somewhere else." And I've always been keen to discover how that String.Chars[] property worked.


SMF spam blocked by CleanTalk