333 lines
13 KiB
C
333 lines
13 KiB
C
|
|
/**
|
||
|
|
* @file runtime_console.c
|
||
|
|
* @brief The group E verbs: SLEEP, WAIT, KEY, WINDOW, and the TI clock.
|
||
|
|
*
|
||
|
|
* Everything here that waits does so by *holding the step loop*, the way GETKEY
|
||
|
|
* already does (see akbasic_input_service). Section 1.6 forbids the library
|
||
|
|
* blocking: a host calls akbasic_runtime_step() once a frame and it must always
|
||
|
|
* come back, so "wait" means "do not advance this step" rather than "do not
|
||
|
|
* return". A bounded akbasic_runtime_run() still returns on time, and a host
|
||
|
|
* that wants to abandon the wait can change the mode.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akbasic/args.h>
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#include "verbs.h"
|
||
|
|
|
||
|
|
/* Most verbs answer "did something happen"; this is that answer. */
|
||
|
|
#define SUCCEED_TRUE(__obj, __dest) \
|
||
|
|
do { \
|
||
|
|
*(__dest) = &(__obj)->staticTrueValue; \
|
||
|
|
} while ( 0 )
|
||
|
|
|
||
|
|
/** @brief Jiffies per second. A Commodore counts time in sixtieths. */
|
||
|
|
#define JIFFIES_PER_SECOND 60
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------ SLEEP -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_sleep(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
double args[1];
|
||
|
|
int count = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in SLEEP");
|
||
|
|
PASS(errctx, akbasic_args_numbers(obj, expr, "SLEEP", args, 1, &count));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count == 1), AKBASIC_ERR_SYNTAX, "Expected SLEEP (seconds)");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (args[0] >= 0.0), AKBASIC_ERR_VALUE,
|
||
|
|
"SLEEP cannot wait a negative number of seconds");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Records when to stop and returns. akbasic_console_service() holds the step
|
||
|
|
* loop until then -- so a program that sleeps still lets its host draw
|
||
|
|
* frames, and a sleeping program in an embedded game does not freeze the
|
||
|
|
* game.
|
||
|
|
*
|
||
|
|
* A host that never calls akbasic_runtime_settime() leaves the clock at
|
||
|
|
* zero, and a deadline computed from a clock that never advances is never
|
||
|
|
* reached. So no clock means no sleep: the verb does nothing rather than
|
||
|
|
* hanging the program, which is the same trade the audio durations make
|
||
|
|
* (§5 deviation 20) and the same direction -- fail fast, not silently
|
||
|
|
* forever.
|
||
|
|
*/
|
||
|
|
if ( obj->timems <= 0 ) {
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
obj->console_state.sleepuntilms = obj->timems + (int64_t)(args[0] * 1000.0);
|
||
|
|
obj->console_state.sleeping = true;
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------------- WAIT -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_wait(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
double args[3];
|
||
|
|
int count = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in WAIT");
|
||
|
|
PASS(errctx, akbasic_args_numbers(obj, expr, "WAIT", args, 3, &count));
|
||
|
|
FAIL_ZERO_RETURN(errctx, (count >= 2), AKBASIC_ERR_SYNTAX,
|
||
|
|
"Expected WAIT (address), (mask) [, (xor)]");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* WAIT polls a byte until `(PEEK(addr) XOR xor) AND mask` is non-zero. On a
|
||
|
|
* C128 that byte is a hardware register an interrupt is changing; here it is
|
||
|
|
* ordinary process memory, and the only thing that can change it is the host
|
||
|
|
* -- or another thread, or a POKE from a TRAP handler.
|
||
|
|
*
|
||
|
|
* So this is honest but rarely useful: a program that waits on memory
|
||
|
|
* nothing writes waits forever, which is exactly what the same program does
|
||
|
|
* on a C128 with the wrong address. It holds the step loop rather than
|
||
|
|
* blocking, so the host keeps its frame rate and can stop the program.
|
||
|
|
*/
|
||
|
|
obj->console_state.waitaddress = (uintptr_t)args[0];
|
||
|
|
obj->console_state.waitmask = (uint8_t)args[1];
|
||
|
|
obj->console_state.waitxor = (count >= 3 ? (uint8_t)args[2] : 0);
|
||
|
|
obj->console_state.waiting = true;
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------------------------------- KEY -- */
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_ASTLeaf *arg = NULL;
|
||
|
|
akbasic_Value *value = NULL;
|
||
|
|
/* Room for the longest macro plus `KEY n, ""` around it. */
|
||
|
|
char line[AKBASIC_MAX_STRING_LENGTH + 32];
|
||
|
|
int64_t number = 0;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
(void)lval; (void)rval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL argument in KEY");
|
||
|
|
|
||
|
|
arg = akbasic_leaf_first_argument(expr);
|
||
|
|
if ( arg == NULL ) {
|
||
|
|
/* Bare KEY lists the definitions, which is what a C128 does. */
|
||
|
|
for ( i = 0; i < AKBASIC_MAX_FUNCTION_KEYS; i++ ) {
|
||
|
|
snprintf(line, sizeof(line), "KEY %d, \"%s\"",
|
||
|
|
i + 1, obj->console_state.keys[i]);
|
||
|
|
PASS(errctx, akbasic_runtime_println(obj, line));
|
||
|
|
}
|
||
|
|
SUCCEED_TRUE(obj, dest);
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||
|
|
FAIL_NONZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||
|
|
"KEY expected a key number");
|
||
|
|
number = value->intval;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (number >= 1 && number <= AKBASIC_MAX_FUNCTION_KEYS),
|
||
|
|
AKBASIC_ERR_BOUNDS, "KEY %" PRId64 " is outside 1..%d",
|
||
|
|
number, AKBASIC_MAX_FUNCTION_KEYS);
|
||
|
|
|
||
|
|
arg = arg->next;
|
||
|
|
FAIL_ZERO_RETURN(errctx, (arg != NULL), AKBASIC_ERR_SYNTAX,
|
||
|
|
"Expected KEY (number), (string)");
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||
|
|