Wednesday | 28 FEB 2024
[ previous ]
[ next ]

Time in ScarletDME

Title:
Date: 2023-11-04
Tags:  zig, scarletdme

I am working on getting the time in milliseconds stuff done for ScarletDME and I spent way more time dealing with Zig than I was expecting. Hopefully I'm actually learning something but it is frustrating when I have to fight the language more than the logic itself.

As a stepping stone, I wanted to rewrite op_time from C to Zig. I figured that this would give me a good base to build out the op_timems function I was envisioning.

Got to go backwards

gplsrc/op_time:

void op_time() {
  InitDescr(e_stack, INTEGER);
  (e_stack++)->data.value = local_time() % 86400L;
}

This is a very simple function. It creates a new entry on the stack, sets the type to INTEGER and then sets the value to the time in seconds mod the number of seconds in a day. This is because we need to get the seconds past midnight. Given the seconds in localtime since epoch, we can figure out how much after midnight by taking out all the full days.

The first hiccup when translating this to zig is that InitDescr is a macro that Zig can't compile. This means that I can't use InitDescr directly, I'd either have to change the macro or change the way I initialize things.

I decided to change how I initialize things, I would mimic what InitDescr was doing directly rather than use the macro.

The next thing was that local_time is a qm function that uses the C localtime function and does some daylight savings time stuff. Luckily I could call this function directly.

The zig code:

const std = @import("std");

const qm = @cImport({
    @cInclude("qm.h");
});

//void op_time() {
//  InitDescr(e_stack, INTEGER);
//  (e_stack++)->data.value = local_time() % 86400L;
//}

export fn op_time() void {
    var localSeconds = @as(f64,@floatFromInt(@rem(qm.local_time(),86400)));
    
    qm.e_stack.*.type = @as(i16, qm.FLOATNUM);
    qm.e_stack.*.data.float_value = localSeconds;
    qm.e_stack = qm.e_stack + 1;
}

As you can see, the code is quit similar except that it is way more explicit than the C version.

The castings are there to make it similar to the timems function below but I could have left this as an integer. That wouldn't have been an issue in this case.

Another hiccup here was that trying to reference things inside the e_stack descr struct requires this strange star syntax. I'm not sure why that's the case.

To go forwards

Now that I had the time function working as it originally was, I then worked on the millisecond version. This was far more painful because I screwed up by first thinking of everything as integers and then because getting time information is just a pain in the ass.

I had spent way too much time trying to figure out why my integers required the special div functions in zig and why I wasn't seeing the decimal. Once I realized that I should be casting to floats, things got much better.

I also didn't realize the special division functions were for integers specifically, I was trying to use them floats and that was also a source of wasted time.

Finding the time function in Zig was also a mission, I did use the C time library but once I found std.os.clock_gettime, I removed the C stuff to stick with zig. I'm sure it's still the same code under the hood.

Below is the time with a ms fraction portion and it looks like it is working perfectly.

export fn op_timems() void {
    var localSeconds = @as(f64,@floatFromInt(@rem(qm.local_time(),86400)));
    
    var time: std.os.timespec = undefined;
    std.os.clock_gettime(0, &time) catch | err |{
        std.debug.print("Zig Error: {any}\n", .{ err });
        qm.process.status = 2;
        return;
    };
    var milliseconds = @as(f64,@floatFromInt(time.tv_nsec))/1_000_000_000;
    
    localSeconds = localSeconds + milliseconds;
    
    qm.e_stack.*.type = @as(i16, qm.FLOATNUM);
    qm.e_stack.*.data.float_value = localSeconds;
    qm.e_stack = qm.e_stack + 1;
}

Pennies

This was a very simple step on my path to modding ScarletDME and I've already learned some very beginner level things. Don't use integers when floats are required, don't use special integer functions against floats, and definitely don't deal with time.

I'm also very much struggling with zig and the castings and this is definitely a lack of knowledge thing. Hopefully the next function I translate won't be as bad.

Now that I have the function, the next step is to wire it up in BASIC, this part should be a different kind of struggle.