27th of April 2021 Porting GS BASIC – to PiTrex (7)

AAAAAAHHHHHRRRGGGG!!!!!

Today I have been chewing on my keyboard. Two “dumb” incidents cost me HOURS to track down.

I started today with adding new commands to Vectrex32 BASIC – mainly “camera”, “rotation”, 3d” etc. In the image above you can see Bobs “DEMO3D” running (now). With the joystick you can rotate the sprites in pretty much all directions.

The first “TRAP”

All the implemenation of BASIC commands and their corresponding VectrexCommands are by now done in a couple of minutes. So I was pretty confident, that I could get huge steps done. I want to get one program running after another, and so finaly implement all functions. I started today with an old “sound” demo of mine – where you can play YM files using BASIC. That was only one function to add… smoothly done!

Than I added a little “extra”, BASIC errors are also displayed on the Vectrex… not everyone has a terminal connected. So I thought that would be pretty neat.

Next to implement was the calls needed for the 3d demo. That were about 10-15 “new” functions. But as said, implementing them took only the better part of half an hour. Than everything compiled without errors and I let it run…

CRASH!

Crashes are bad. In many ways, not only that the program is not running. Programming in baremetal is always a little bit like living on the edge. Your hands are pretty much tight up, you can not “really” debug. All you can do is “print variable” – and circle in on the problem. The last shown “print” is near the bug.

After print stepping my way from BASIC to BASIC functions to VectrexCommands – the final culprit was found within the “Camera” class. The camera class holds a variable called “_translate”, which – well – is amongst others used to calculate the current viewport. The thing is – all the other variables are basic values (int, float etc…) that one is the only “class” – one instantiation of the class “Value”. The translation can internally have up to 3 coordinates. But that is beside the point now.

The “Value” class is an “abstraction” data class, which can hold many things. Arrays, structs, integers, strings etc. Each of those data formats has a “behaviour”. The behavious is again defined in a behaviour class.

Now… here we are… the

  • behaviour
  • of the Value
  • which _translate is an instantiation from
  • within the camera
  • was NULL

If you look at the source code, you might notice, that that is theoretically impossible. Since the value is instantiated upon the construction of the camera, and each value ALWAYS has a behaviour. The camera on the other hand is instantiated at runtime, since it is a global variable.

Ok… I’ll shorten this a bit, because it will get (more) embarassing – the longer I continue with this. The truth is – the camera got only “half” instantiated at startup. If you look at the first blog entry concerning my porting – it was in the spirit “setting things up for c++”… I did “forget” to set one thing up.

I did not change my runtime system to call “global” and “static” constructors upon starting. Because of that it was possible to have a “live” camera – but one which had not called its own constructor, so several variables (and objects) were only partly initialized.

I either have to change all global and static variables to runtime created ones – or setup my system to call those damn constructors.

I searched the web for quite some time… and now my main() starts with the following:

extern void (preinit_array_start []) (void) __attribute((weak)); 
extern void (preinit_array_end []) (void) __attribute((weak));
extern void (init_array_start []) (void) __attribute((weak)); 
extern void (init_array_end []) (void) __attribute((weak));
int main(int argc, char *argv[])
{
   size_t count;
   size_t i;
   count = __preinit_array_end - __preinit_array_start;
   for (i = 0; i < count; i++)
     __preinit_array_start[i] ();

   count = __init_array_end - __init_array_start;
   for (i = 0; i < count; i++)
     __init_array_start[i] ();
   ...

This sets up all constructors and calls them… now I should “really” be ready to do c++.
This “feature” escaped me before – since the camera was the first class to be globally instantiated.

The second “TRAP”

NOW, surely… everything compiles – it will work!

CRASH!

Seemingly at exactly the same position. Ah, well, actually not, which was discovered after a few dozen meaningfull printf statements scattered around the code.
I will also shorten this. But let me just say this: I HATE VARIADIC FUNCTIONS

Always have and always will. At least for me they are completely unintuitive.
Following function:

void Matrix::Initialize(float first, …)
 {
     float *data = _data;
     size_t count = Rows() * Columns();
     va_list list;
     va_start(list, first);
     *data++ = first;
     while (--count)
     {
         *data++ = (float) va_arg(list, double);
     }
     va_end(list);
 }

… gave me nightmares. The “list” was always NULL. I have spent quite a lot of time trying to figure out what was happening. But those macros are not debugable, because they resolve to internal gcc functions. I don’t know what is wrong with it. After quite some time I just chose to give up.

Luckily c++ can preinitialize function parameters. With an Initialize() function specified as e.g.:

void Initialize(float first, float f1,float f2, float f3=0, float f4=0, float f5=0, float f6=0, float f7=0, float f8=0, float f9=0, float f10=0, float f11=0, float f12=0, float f13=0, float f14=0, float f15=0);

and an implementation as:

void Matrix::Initialize(float first, float f1,float f2, float f3, float f4, float f5, float f6, float f7, float f8, float f9, float f10, float f11, float f12, float f13, float f14, float f15)
 {
   int c=0;
   float *data = _data;
   size_t count = Rows() * Columns();
   *(data+(c++)) = (float) first;
   if (c<count) *(data+(c++)) = (float) f1;
   if (c<count) *(data+(c++)) = (float) f2;
   if (c<count) *(data+(c++)) = (float) f3;
   if (c<count) *(data+(c++)) = (float) f4;
   if (c<count) *(data+(c++)) = (float) f5;
   if (c<count) *(data+(c++)) = (float) f6;
   if (c<count) *(data+(c++)) = (float) f7;
   if (c<count) *(data+(c++)) = (float) f8;
   if (c<count) *(data+(c++)) = (float) f9;
   if (c<count) *(data+(c++)) = (float) f10;
   if (c<count) *(data+(c++)) = (float) f11;
   if (c<count) *(data+(c++)) = (float) f12;
   if (c<count) *(data+(c++)) = (float) f13;
   if (c<count) *(data+(c++)) = (float) f14;
   if (c<count) *(data+(c++)) = (float) f15;
 }

One still needs only one Initialize function for all current instatiated Matrix variables.

It looks immensly stupid. But it works! And I do not have to look those stupid va_args in the eyes anymore.

I scanned the sources… this is the only time Bob used variadic parameters. With the constructors being called correctly now … I hope I will be able to make greater “porting leaps” from tomorrow on.

Cheers

Malban

Tagged on: ,

2 thoughts on “27th of April 2021 Porting GS BASIC – to PiTrex (7)

  1. Bob

    Speaking of cameras… you might come across some code that implements two cameras. I started on, but never finished, support for the 3D imager. It would be real easy to use: the V32 would set up a left camera and a right camera, and everything just works, with the V32 alternating between the two camera views with each frame. The programmer wouldn’t have to do anything difficult: just set up a 3D world (like you would for any 3D game like Battle Zone) and make a single call to turn on 3D Imager support.

    One of the challenges was syncing with the rotating wheel. It seemed really hard, especially since the V32 is so far removed from the 6809. As they communicate through the DPRAM, the V32 is always on the next frame while the 6809 is drawing the previous frame.

    Perhaps this could finally be accomplished with the PiTrex.

    1. Avi

      Bob, could the instructions to the Vectrex include both frames with instructions as to which frame to draw when the wheel hits its trigger? That way, the V32 doesn’t have to worry about syncing with the wheel?

      Am I thinking about that correctly?

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.