Torus Trooper - Rebooting a 15 year-old game written in D - Part 2 Running
See also
- Part 1 - Compiling a new executable
- Part 2 - Running the game for the first time
- Part 3 - Porting to WebAssembly
- Part 4 - Final steps
From part 1, I stopped after successfully compiling a new executable, but will it run?
Answer is… NO!
Alright, let’s dig into it!
D1’s std.file.listdir
core.exception.RangeError@src\abagames\tt\barrage.d(110): Range violation
----------------
0x00448944 in _d_newarrayU
0x0041734B in void abagames.tt.barrage.Barrage.addBml(immutable(char)[], immutable(char)[], float, bool, float)
D1 used to have a function named listdir
in Phobos. It can’t be found in undead
unfortunately. However, a quick search later here is what I found in the official docs:
I thought “great!”, thanks to the contributor who thought of exactly this case!
And then I found out that the crash above was because none of the XML files for BulletML were loading. A quick “debug print” session revealed that listdir
only returned the list of files in a path. Is that the original behaviour? Let’s check… it wasn’t!
Well the fix was easy at least:
|
|
Cyclic dependency between modules constructors/destructors
object.Error@src\rt\minfo.d(371): Cyclic dependency between module constructors/destructors of abagames.tt.enemy and abagames.tt.barrage
abagames.tt.enemy* ->
abagames.tt.barrage* ->
abagames.tt.bulletactor ->
abagames.tt.enemy*
From what I could understand, this comes from the fact that when using static constructors in classes, the D runtime has no way of knowing if they have a dependency on each other. There are ways to disable the check if you’re sure of your code (see the thread I started there). However, looking at the code, it seems like I could just replace the static constructors.
What it was used for before:
|
|
Because of the replay feature, none of the Rand
instances were accessed without calling setRandSeed
. So removing the constructor and initializing the static rand
member on demand seemed appropriate:
|
|
No cyclic dependency error anymore!
Code smell: using static state for non-static code
This isn’t technically required to get the game to run but in my opinion it needed fixing to avoid problems later on. From investigating the previous item regarding Rand
, I found that some code was setting the seed of a static Rand rand
member in the constructor and then use it during the non-static call to create
in that same class. This is usually a code smell as it means any future code would modify this behaviour. I agree it’s unlikely in a single-threaded code that it has any impact but fixing it made me feel better about the state of the codebase.
Here is what I’m talking about:
|
|
And it’s used like this:
|
|
I have fixed this by creating a local Rand
instance inside create
and pass that to the appropriate functions.
|
|
|
|
Associative arrays membership test
D1 had associative arrays that worked like C++'s std::map
where you check for ownership by checking if there is a value. That got replaced with the in
operator. It means a little more checking as you need to check both levels in case you have a multidimensional associative array but it’s reasonable and probably clearer.
|
|
First gameplay!
I can now run the game! However, not for long, or at least it’s not very entertaining.
Indeed it crashes whenever you destroy a big enemy… let’s see the error:
object.Error@(0): Access Violation
----------------
0x00425F9F in void abagames.tt.shape.ShipShape.addFragments(abagames.util.vector.Vector, abagames.tt.particle.ParticlePool) at src\abagames\tt\shape.d(317)
0x0041FFF5 in void abagames.tt.enemy.Enemy.destroyed() at src\abagames\tt\enemy.d(327)
0x0041FCE7 in void abagames.tt.enemy.Enemy.checkShotHit(abagames.util.vector.Vector, abagames.tt.shape.Collidable, abagames.tt.shot.Shot) at src\abagames\tt\enemy.d(297)
0x00420342 in void abagames.tt.enemy.EnemyPool.checkShotHit(abagames.util.vector.Vector, abagames.tt.shape.Collidable, abagames.tt.shot.Shot) at src\abagames\tt\enemy.d(394)
0x0042BF45 in void abagames.tt.shot.Shot.move()
Here is the fix:
|
|
Of course, it’s the code smell I preemptively tried to fix earlier…
I suspected my original fix might have had consequences down the line, at least this was easy to figure out. However, it shows you can’t make assumptions about old code like this, everything has a purpose, don’t fix it if it doesn’t need fixing! My assumption here was that the ShipShape
class had its Rand
initialized the same way as the others but it hadn’t…
Wrapping up part 2
With this, the game is playable as far I could tell. Replays work, saves work and it behaves the same as the original executable!
However, the original only supplied a Windows executable. The original source code shows it might have worked on other systems but the libs for this weren’t supplied. To be able to port the game to other platforms, the main issue I can think of would be the BulletML library which is a custom dynamic library.
I think I found the original C++ code for BulletML which I’ll have a look at in part 3 where I will try to make the codebase support multiple systems.