[Solved] Null Exception with chained managed struct attribute (readonly)

Started by Snarky, Sat 21/10/2017 01:24:39

Previous topic - Next topic

Snarky

Another thing that seems to break the AGS scripting language: chaining together attributes that represent dynamically generated managed structs.

I'll explain. I have a managed struct that just holds a bunch of related data (I use a managed struct so I can put it inside various other structs and arrays):

Code: ags
managed struct Data
{
  int a;
  int b;
};


However, I also need to include this in some other managed structs. Since you can't store a managed struct in a managed struct, I've added some functions to convert Data to array form:

Code: ags
// I've used a static extender function, available with AGS 3.4, but it's the same with a regular static function

Data* fromArray(static Data, int array[])
{
  Data* d = new Data;
  d.a = array[0];
  d.b = array[1];
  
  return d;
}

int[] toArray(this Data*)
{
  int array[] = new int[2];
  array[0] = this.a;
  array[1] = this.b;
  return array;
}


Now I can store it in an array inside my other managed structs, and use an attribute with getters and setters calling the conversion methods to make the whole thing transparent:

Code: ags
// Header
managed struct Primary
{
  // Underlying array
  protected int _data[2];

  // Attribute, accessors
  import attribute Data* data;
  import Data* get_data();        // $AUTOCOMPLETEIGNORE$
  import void set_data(Data* d);  // $AUTOCOMPLETEIGNORE$};

// Script

Data* Primary::get_data()
{
  // Has to be a dynamic array, so make a copy
  int array[] = new int[2];
  for(int i=0; i<2; i++)
    array[i]=this._data[i];

  return Data.fromArray(array);
}

// Note this works as pass-by-value, not reference
void Primary::set_data(Data* d)
{
  if(d != null)
  {
    this._data[0] = d.a;
    this._data[1] = d.b;
  }
}


This works fine so far:

Code: ags
  Data* raw = new Data;
  raw.a = 2; raw.b = 3;
  
  Primary* p = new Primary;
  p.data = raw;

  // These should display the same
  Display("Raw data a:%d b:%d", raw.a, raw.b);            // Displays: "Raw data a:2 b:3"
  Display("Primary data a:%d b:%d", p.data.a, p.data.b);  // Displays: "Raw data a:2 b:3"


However, I also have another managed struct that uses Data. The actual link is a bit complex, but basically it's supposed to use the Data values associated with a particular Primary struct that is looked up in an array based on other fields; the Data* attribute is therefore read-only. So I tried to do something like this:

Code: ags
// Header
import Primary* primaries[10];

managed struct Secondary
{
  int primaryIndex;    // index into primaries[]
  
  // Attribute, accessor
  import readonly attribute Data* data;
  import Data* get_data();        // $AUTOCOMPLETEIGNORE$
};

// Script
Data* Secondary::get_data()
{
  if(this.primaryIndex != -1)
  {
    Data* d = primaries[this.primaryIndex].data;
    return d;
  }
  return null;
}


So, putting it all together, I try this:

Code: ags
  Data* raw = new Data;
  raw.a = 2; raw.b = 3;
  
  primaries[0] = new Primary;
  primaries[0].data = raw;
  
  Secondary* s = new Secondary;
  s.primaryIndex = 0;
  
  Display("Raw data a:%d b:%d", raw.a, raw.b);
  Display("Primary data a:%d b:%d", primaries[0].data.a, primaries[0].data.b);
  Display("Secondary data a:%d b:%d", s.data.a, s.data.b); // CRASH!


The last line crashes with a null exception. After some testing, it seems that s.data is always null. Doing exactly the same calculation outside of the attribute accessor works:

Code: ags
  Data* test1 = s.data;                            // This will be null...
  Data* test2 = primaries[s.primaryIndex].data;    // ... while this will be a valid object


So is it a problem with trying to "chain" attribute accessors together like this? If I rewrite Secondary::get_data() as a direct copy of Primary::get_data() (making the _data[] array not protected and accessing it directly), it works, but it's an ugly workaround.

I've also got an inkling that it could have something to do with the readonly tag on the attribute. Oddly, when I take it away and try to provide a dummy setter, I get a hard crash (i.e. not to debugger) at an earlier line instead:

Quote---------------------------
Illegal exception
---------------------------
An exception 0xC0000005 occurred in ACWIN.EXE at EIP = 0x00401633 ; program pointer is +1101, ACI version 3.4.0.16, gtags (0,0)

AGS cannot continue, this exception was fatal. Please note down the numbers above, remember what you were doing at the time and post the details on the AGS Technical Forum.

in "AttributeTest.asc", line 49
from "room1.asc", line 54
from "room1.asc", line 63


Most versions of Windows allow you to press Ctrl+C now to copy this entire message to the clipboard for easy reporting.

An error file CrashInfo.dmp has been created. You may be asked to upload this file when reporting this problem on the AGS Forums. (code 0)
---------------------------
OK   
---------------------------

The line reference is to Data* d = primaries[this.primaryIndex].data; in Secondary::get_data().

I don't really have a good understanding of what's going on.

Crimson Wizard

I recall there is an issue that you cannot use attributes in the same module where you defined their accessors. In that module you should only use get_/set_ functions.
Is it the case?

Snarky

Uh, yeah.

Damn, I'd forgotten about that! I thought I'd tried everything, but it was such a simple thing. Thanks, CW!

SMF spam blocked by CleanTalk