I ran into an assertion in the script interpreter when testing a dhewm3 mod in a Debug build: "st->c->value.argSize == func->parmTotal" from idInterpreter::Execute()
(Please read this if you're familiar with Doom3 scripting; I will explain what seems to go wrong in C++, but you can ignore that part, I'd just like to know if this issue is know and/or considered normal. Thanks!

It turned out that the problem was the following:
We have a player "class" like:
object player : player_base {
// .. lots of stuff not important for the issue
void init();
float music_volume;
void check_music_volume();
// ... and more details
};
and then below there's implementations for those methods - and
init() creates a thread with
check_music_volume(), but the
check_music_volume() implementation is below the
init() implementation!
void player::init() {
// .. whatever ..
thread check_music_volume();
// ...
}
// ...
void player::check_music_volume() {
// ... does some stuff in an endless loop ...
while(1) {
if(music_volume > 0) {
// .. do whatever ..
}
}
}
Now when I start a map and that code gets executed (player::init() is called when the player spawns), I get the aforementioned assertion (if assertions are enabled, like in debug builds), because the opcode pretends that the function call has no arguments at all (that need to be passed when calling), while in reality the "self" pointer to the player object is implicitly passed (so check_music_volume() knows what player object it belongs to and can access its fields, like
music_volume).
This can be "fixed" by moving
void player::check_music_volume() { ... }abovevoid player::init() { ... }Is it a known problem or considered normal, that the order of function/method implementations matters if one function calls another function?(Following the C++ analysis:)
Now the problem was that the script compiler, when generating the ops for that thread creation
(
idCompiler::ParseObjectCall() => idCompiler::EmitFunctionParms(), op == OP_OBJTHREAD)
it sets the wrong size for the statement, taken from
func->value.functionPtr->parmTotal.
This happens at game startup, when all the scripts are parsed and compiled.
The reason that
parmTotal for the "check_music_volume" function_t is
0 (while it should be
8, at least on my machine, for the
self object reference) is, that even though an
idTypeDef with type
ev_function and the
function_t is created when
object player : player_base { is parsed and it encounters the
void check_music_volume(); line,
parmTotal is not calculated and set until the
implementation of the function is parsed much later (when it reaches
void player::check_music_volume() { way below).
It all happens in
idCompiler::ParseFunctionDef(...) - that function is called
both for the function declaration/prototype (
void check_music_volume();) is called from
idCompiler::ParseObjectDef() when it parses the
player object,
and later when parsing the
implementation (after the bytecode for
player::init() is generated), in that case it's called from
ParseNamespace() => ParseDefs() => ParseFunctionDef().
The reason the second call calculates and sets
parmTotal (and also
numParams) correctly (and the first call doesn't) is the following lines in
idCompiler::ParseFunctionDef(...):
// check if this is a prototype or declaration
if ( !CheckToken( "{" ) ) {
// it's just a prototype, so get the ; and move on
ExpectToken( ";" );
return;
}
// calculate stack space used by parms
numParms = type->NumParameters();
func->parmSize.SetNum( numParms );
for( i = 0; i < numParms; i++ ) {
parmType = type->GetParmType( i );
if ( parmType->Inherits( &type_object ) ) {
func->parmSize[ i ] = type_object.Size();
} else {
func->parmSize[ i ] = parmType->Size();
}
func->parmTotal += func->parmSize[ i ];
}
// define the parms
// ... etc ...
So when parsing the method
declaration/prototype ParseFunctionDef(...) returns before doing the calculations for stack space needed by function parameters - this is only done when parsing the
implementation!
So, if the method is called in the script file
before the the implementation,
assert( st->c->value.argSize == func->parmTotal ); in
idInterpreter::Execute() fails.
I have no idea why the stack space calculation etc isn't just done when parsing the prototype.