Making this an object had little to no advantage, except being able to remove the deleter code. Now, with some of the class data already being allocated in a memory arena so that freeing it is easier, this can also be used for the drop item lists which makes it unnecessary to subject them to the GC. This also merges the memory arenas for VM functions and flat pointers because both get deleted at the same time so they can share the same one.
As it stood, just compiling the internal ZScript code created more than 9000 DObjects, none of which really need to be subjected to garbage collection, aside from allowing lazy deallocation.
This puts an incredible drag on the garbage collector which often needs several minutes to finish processing before actual deletion can start.
The VM functions with roughly 1800 of these objects were by far the easiest to refactor so they are now. They also use a memory arena now which significantly reduces their memory footprint.
- use a memory arena to store flat pointers so that the messed up cleanup can be avoided by deallocating this in bulk.
- added a new SO opcode to the VM to execute a write barrier. This is necessary for all objects that are not linked into one global table, i.e. everything except thinkers and class types.
- always use the cheaper LOS opcode for reading pointers to classes and defaults because these cannot be destroyed during normal operation.
- removed the pointless validation from String.Mid. If the values are read as unsigned the internal validation of FString::Mid will automatically ensure proper results.
error: use of undeclared identifier 'op'
error: no matching function for call to 'ListEnd'
error: no matching function for call to 'ListGetInt'
error: no matching function for call to 'ListGetDouble'
...
This isn't done for register based variables so for consistency it should not be done for stack based variables, too.
This difference in handling made it impossible to check the target of a hitscan attack if it was destroyed by getting damaged.
If a later module reused an existing name for a different class or struct type, this new name would completely shadow the old one, even in the base files.
Changed it so that each compilation unit (i.e. each ZScript and DECORATE lump) get their own symbol table and can only see the symbol tables that got defined in lower numbered resource files so that later definitions do not pollute the available list of symbols when running the compiler backend and code generator - which happens after everything has been parsed.
Another effect of this is that a mod that reuses the name of an internal global constant will only see its own constant, again reducing the risk of potential errors in case the internal definitions add some new values.
Global constants are still discouraged from being used because what this does not and can not handle is the case that a mod defines a global constant with the same name as a class variable. In such a case the class variable will always take precedence for code inside that class.
Note that the internal struct String had to be renamed for this because the stricter checks did not let the type String pass on the left side of a '.' anymore.
- made PEnum inherit from PInt and not from PNamedType.
The old inheritance broke nearly every check for integer compatibility in the compiler, so this hopefully leads to a working enum implementation.
- fixed the return type checks in CallStateChain. These made some bogus assumptions about what return prototypes to support and would have skipped any multi-return function whose first argument was actually usable.
- improved the class pointer to string cast to print the actual type it describes and not the class pointer's own type.
- fixed: The 'is' operator created non-working code when checking the inheritance of a class pointer, it only worked for objects.
There are a few which require explicit native construction or destruction that need to be exported to the VM, e.g. FCheckPosition.
The VM cannot handle this directly, it needs two special functions to be attached to handle such elements.
- made ModifyDamage calls iterative instead of recursive. With going through the VM they'd be too costly otherwise.
- small optimization: Detect empty VM functions right when entering the VM and shortcut them. This is to reduce the overhead of virtual placeholders, which in a few cases (e.g. CanCollideWith and ModifyDamage) can be called quite frequently.
This can see some heavy use in iterators where saving several hundreds of function calls can be achieved. In these cases, using a function to do the job will become a significant time waster.
The original implementation just printed a mostly information-free message and then went on as if nothing has happened, making it ridiculously easy to write broken code and release it. Changed it to:
* Any VMAbortException will now terminate the game session and go back to the console.
* It will also print a VM stack trace with all open functions, including source file and line numbers pointing to the problem spots. For this the relevant information had to be added to the VMScriptFunction class.
An interesting effect here was that just throwing the exception object increased the VM's Exec function's stack size from 900 bytes to 70kb, because the compiler allocates a separate local buffer for every single instance of the exception object.
The obvious solution was to put this part into a subfunction so that it won't pollute the Exec function's own stack frame. Interesting side effect of this: Exec's stack requirement went down from 900 bytes to 600 bytes. This is still on the high side but already a lot better.
- fixed PARAM_ACTION_PROLOGUE to assign correct types to the implicit pointers. It gave the actual class to the wrong one, which until now did not matter because all functions were using 'Actor', regardless of actual class association.
- fixed the definition of IceChunk and removed some redundant code here. Since A_FreezeDeathChunks already calls SetState, which in turn calls the state's action function, there is no need to call it again explicitly.
- throw a useful exception when a VM abort occurs, the simple enum was incapable of reporting anything more than the barest minimum, which at least for array index out of bounds errors was insufficient.
The current exception mechanism is still insufficient. It really has to report a proper crash location and print a stack trace to the maximum extent possible. Instead it just prints a message and happily goes on. This is not a good solution.
Although this already helps a lot with the messed up code generated for comparisons it's not really a solution for this - it still needs a proper implementation to generate efficient code.
It is utterly pointless to require every function that wants to make a VM call to allocate a new stack first. The allocation overhead doubles the time to set up the call.
With one stack, previously allocated memory can be reused. The only important thing is, if this ever gets used in a multithreaded environment to have the stack being declared as thread_local, although for ZDoom this is of no consequence.
- eliminated all cases where native code was calling other native code through the VM interface. After scriptifying the game code, only 5 places were left which were quickly eliminated. This was mostly to ensure that the native VM function parameters do not need to be propagated further than absolutely necessary.
- added a String class to allow attaching methods to the builtin string type. This works by checking if the left side of the member accessor is a string and just replacing the tyoe in this one place, all the rest is automatic.