all repos — xyw @ eb2365b3bdfc2a620e2da1a4975eab4b60910efe

A minimal virtual machine and assembler for terminals.

devices/clock.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif
#include "../xyw.h"

/* clock device */
#define CLOCK_YEAR 0x0
#define CLOCK_MONTH 0x2
#define CLOCK_MONTHDAY 0x3
#define CLOCK_WEEKDAY 0x4
#define CLOCK_CLOCK_HOUR 0x5
#define CLOCK_MINUTE 0x6
#define CLOCK_SECOND 0x7
#define CLOCK_TIMER 0x8
#define CLOCK_ON_TIMER_ELAPSED 0xA

static int clock_timer_active = 0;
static int clock_timer_elapsed_pending = 0;

#ifdef _WIN32
static LARGE_INTEGER clock_timer_freq;
static LARGE_INTEGER clock_timer_target; // Target time when timer elapses
#else
static struct timespec clock_timer_target; // Target time when timer elapses
#endif

// Get current timer value (remaining ticks until target)
static xyw_word clock_get_timer_value(void)
{
    if (!clock_timer_active)
        return 0;

#ifdef _WIN32
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    // Calculate remaining time in 1/256 second units
    if (now.QuadPart >= clock_timer_target.QuadPart)
    {
        // Timer has elapsed
        clock_timer_active = 0;
        clock_timer_elapsed_pending = 1;
        return 0;
    }
    double remaining_sec = (double)(clock_timer_target.QuadPart - now.QuadPart) / clock_timer_freq.QuadPart;
    xyw_word remaining_ticks = (xyw_word)(remaining_sec * 256.0);
    if (remaining_ticks == 0)
    {
        // Less than 1 tick remaining, consider elapsed
        clock_timer_active = 0;
        clock_timer_elapsed_pending = 1;
        return 0;
    }
    return remaining_ticks;
#else
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    // Calculate remaining time in nanoseconds
    long long remaining_ns = (clock_timer_target.tv_sec - now.tv_sec) * 1000000000LL +
                             (clock_timer_target.tv_nsec - now.tv_nsec);
    if (remaining_ns <= 0)
    {
        // Timer has elapsed
        clock_timer_active = 0;
        clock_timer_elapsed_pending = 1;
        return 0;
    }
    // Convert to 1/256 second units (1/256 sec = 3906250 ns)
    xyw_word remaining_ticks = (xyw_word)(remaining_ns / 3906250);
    if (remaining_ticks == 0)
    {
        // Less than 1 tick remaining, consider elapsed
        clock_timer_active = 0;
        clock_timer_elapsed_pending = 1;
        return 0;
    }
    return remaining_ticks;
#endif
}

#define CLOCK_POLL_SLEEP_MS 100

int clock_poll(xyw_byte *data)
{
    xyw_word on_timer_handler = XYW_PEEKW(&data[CLOCK_ON_TIMER_ELAPSED]);
    if (!on_timer_handler)
        return 0; // no handler registered

    // Check if timer has elapsed (non-blocking)
    clock_get_timer_value();
    if (clock_timer_elapsed_pending)
    {
        clock_timer_elapsed_pending = 0;
        xyw_vm_set_running(1);
        xyw_vm_push_frame(XYW_DEVICE_AREA_START);
        *pc = on_timer_handler;
        return 1;
    }

    if (!clock_timer_active)
        return 0; // no timer running

    // Timer is active but hasn't elapsed yet — sleep briefly to avoid busy loop.
    // When terminal_poll is also active, it provides its own 100ms sleep via
    // getch_timeout, so this sleep only matters for timer-only programs.
#ifdef _WIN32
    Sleep(CLOCK_POLL_SLEEP_MS);
#else
    usleep((useconds_t)(CLOCK_POLL_SLEEP_MS * 1000));
#endif
    return -1; // active handler, no event yet
}

void clock_setup(xyw_byte *data)
{
    (void)data;
    clock_timer_active = 0;
    clock_timer_elapsed_pending = 0;
#ifdef _WIN32
    QueryPerformanceFrequency(&clock_timer_freq);
#endif
}

xyw_byte clock_input(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
    (void)error;
    (void)data;

    /* Handle timer reads */
    if (addr == CLOCK_TIMER)
    {
        xyw_word val = clock_get_timer_value();
        return (xyw_byte)(val >> 8);
    }
    if (addr == CLOCK_TIMER + 1)
    {
        xyw_word val = clock_get_timer_value();
        return (xyw_byte)(val & 0xFF);
    }
    // Reading CLOCK_ON_TIMER_ELAPSED blocks until timer elapses (if active)
    if (addr == CLOCK_ON_TIMER_ELAPSED)
    {
        // If timer is active, block until it elapses using absolute target time
        if (clock_timer_active)
        {
            xyw_word remaining = clock_get_timer_value();
            if (remaining > 0)
            {
#ifdef _WIN32
                // Calculate remaining time and sleep
                LARGE_INTEGER now;
                QueryPerformanceCounter(&now);
                if (clock_timer_target.QuadPart > now.QuadPart)
                {
                    double remaining_sec = (double)(clock_timer_target.QuadPart - now.QuadPart) / clock_timer_freq.QuadPart;
                    DWORD ms = (DWORD)(remaining_sec * 1000.0);
                    if (ms > 0)
                        Sleep(ms);
                }
#else
                // Use clock_nanosleep with absolute target time for precision
                clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &clock_timer_target, NULL);
#endif
                // After sleep, update timer state
                clock_get_timer_value();
            }
        }
        // Return and clear the pending flag
        xyw_byte pending = clock_timer_elapsed_pending ? 1 : 0;
        clock_timer_elapsed_pending = 0;
        return pending;
    }

    time_t now = time(NULL);
    struct tm *lt = localtime(&now);
    if (!lt)
        return 0;

    switch (addr)
    {
    case CLOCK_YEAR:
        return (xyw_byte)((lt->tm_year + 1900) >> 8);
    case CLOCK_YEAR + 1:
        return (xyw_byte)((lt->tm_year + 1900) & 0xFF);
    case CLOCK_MONTH:
        // 1-12
        return (xyw_byte)(lt->tm_mon + 1);
    case CLOCK_MONTHDAY:
        // 1-31
        return (xyw_byte)lt->tm_mday;
    case CLOCK_WEEKDAY:
        // 0=Sunday ... 6=Saturday
        return (xyw_byte)lt->tm_wday;
    case CLOCK_CLOCK_HOUR:
        // 0-23
        return (xyw_byte)lt->tm_hour;
    case CLOCK_MINUTE:
        return (xyw_byte)lt->tm_min;
    case CLOCK_SECOND:
        return (xyw_byte)lt->tm_sec;
    default:
        return 0;
    }
}

void clock_output(xyw_byte *data, xyw_byte addr, xyw_byte *error)
{
    (void)error;

    /* When writing to CLOCK_TIMER+1 (check only last byte, so that this doesn't get fired twice, 
       once for each byte), start/restart the timer */
    if (addr == CLOCK_TIMER + 1)
    {
        // Read the full 16-bit timer value from device data
        xyw_word timer_val = XYW_PEEKW(&data[CLOCK_TIMER]);

        if (timer_val > 0)
        {
            clock_timer_active = 1;
            clock_timer_elapsed_pending = 0;
            // Calculate target time: now + timer_val * (1/256 second)
#ifdef _WIN32
            LARGE_INTEGER now;
            QueryPerformanceCounter(&now);
            // target = now + (timer_val / 256) seconds in performance counter units
            long long ticks_to_add = (long long)timer_val * clock_timer_freq.QuadPart / 256;
            clock_timer_target.QuadPart = now.QuadPart + ticks_to_add;
#else
            struct timespec now;
            clock_gettime(CLOCK_MONOTONIC, &now);
            // 1/256 second = 3906250 nanoseconds
            long long ns_to_add = (long long)timer_val * 3906250LL;
            clock_timer_target.tv_sec = now.tv_sec + (ns_to_add / 1000000000LL);
            clock_timer_target.tv_nsec = now.tv_nsec + (ns_to_add % 1000000000LL);
            // Handle nanosecond overflow
            if (clock_timer_target.tv_nsec >= 1000000000L)
            {
                clock_timer_target.tv_sec++;
                clock_timer_target.tv_nsec -= 1000000000L;
            }
#endif
        }
        else
        {
            // Writing 0 deactivates the timer
            clock_timer_active = 0;
            clock_timer_elapsed_pending = 0;
        }
    }
}