Read-only archive of the Armageddon Games Forums Archive (2000–2026) · About this archive

[ZScript] "this" pointer doesn't work correctly in internal methods

6 replies · 1,208 views · Started 28 September 2006

The following script will produce a warning when compiled:


ffc script One {
void Two() {
this->X++;
}
void run() {
Two();
}
}
The warning is "INTERNAL ERROR: CAN'T FIND VARIABLE STACK OFFSET".

Then, when you run it, it doesn't work correctly.

Yep, anything with "internal error" is going to be a bug. I'll look into it.

In the meantime passing the pointer explicitly should suffice as a workaround:


ffc script One {
void Two(ffc ptr) {
ptr->X++;
}
void run() {
Two(this);
}
}

OK, this bug is more serious than expected. Here's the problem: ZScripts are a compile-time fiction. For example, consider the following scripts:


ffc script a {
voir run() {a();}
void a() {...}
void b() {...}
}

ffc script b {
void run() {a.a(); a.b();}
void c() {...}
}


When this source is compiled, two ASM "chunks" are created: a's run script, together with the code from a.a(), and b's run script, with a second copy of a.a() and a.b(). b.c() is completely ignored and missing entirely from the output. Basically, the compiler determines what methods are called by what scripts, and slaps on those methods accordingly.

So for the bug at hand, the problem is that it's not clear what "this" should point to when you call a non-run method. There are three options:
1. Helper methods cannot use the this pointer. It must be passed in explicitly, as in my above post.
2. The this pointer is passed implicitly. There are two dangers to this approach. First, it's easy to get confused about what the this pointer points to. For instance,


ffc script a {
void run() {}
void helper() {this->X = 100;}
}

ffc script b {
void run() {a.helper();}
}


When b's script is run, it is the ffc associated with b, not a, that is moved.
Second, calling a method of a different type of script is disastrous if that method uses the this pointer. For instance, the following snippet

item script a {
void run() {}
void helper() {this->X = 100;}
}

ffc script b {
void run() {a.helper();}
}


will experience undefined behavior and probably crash.
For these reasons I'm leaning towards option 1, although I am also open to
3. Something completely different.

Thoughts?

3: Scripts can't call methods in other scripts.

You said it yourself: on the ASM level, there is no "call" anyway. All that "calling" actually does is copy the entire called method into the caller's code at compile time. So, why not be honest about it by forcing people to perform the act of copying by hand? (Or rather, force them to make such methods into global functions, like they should be.)

Unfortunately, I've run out of time before getting around to patching this bug. L, feel free to try your hand at understanding and modifying the ZScript code 😞

The compiler will now prevent you from trying to use this anywhere outside of run(). If you want to use it elsewhere, pass it explicitly as a pointer.

I've fixed other scoping problems as well, such as Link, Game, etc not working in global functions.