]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Merge branch 'arithmetic_exceptions' into cooking
authorDale Weiler <weilercdale@gmail.com>
Sun, 25 May 2014 07:01:47 +0000 (03:01 -0400)
committerDale Weiler <weilercdale@gmail.com>
Sun, 25 May 2014 07:01:47 +0000 (03:01 -0400)
Conflicts:
doc/gmqcc.1
gmqcc.ini.example
opts.def
parser.c

16 files changed:
ast.c
ast.h
doc/gmqcc.1
exec.c
gmqcc.h
gmqcc.ini.example
ir.c
ir.h
main.c
opts.c
opts.def
parser.c
test.c
tests/state-emu.tmpl [new file with mode: 0644]
tests/state.qc [new file with mode: 0644]
tests/state.tmpl [new file with mode: 0644]

diff --git a/ast.c b/ast.c
index 5c38fa96ea4819319bef814cf819005d734057b3..24d0479a71f0b6ae956cce8aa3e264ebdff5cc1c 100644 (file)
--- a/ast.c
+++ b/ast.c
@@ -75,6 +75,7 @@ static void ast_binstore_delete(ast_binstore*);
 static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
 static void ast_binary_delete(ast_binary*);
 static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
+static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**);
 
 /* It must not be possible to get here. */
 static GMQCC_NORETURN void _ast_node_destroy(ast_node *self)
@@ -973,6 +974,26 @@ void ast_goto_set_label(ast_goto *self, ast_label *label)
     self->target = label;
 }
 
+ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think)
+{
+    ast_instantiate(ast_state, ctx, ast_state_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen);
+    self->framenum  = frame;
+    self->nextthink = think;
+    return self;
+}
+
+void ast_state_delete(ast_state *self)
+{
+    if (self->framenum)
+        ast_unref(self->framenum);
+    if (self->nextthink)
+        ast_unref(self->nextthink);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
 ast_call* ast_call_new(lex_ctx_t ctx,
                        ast_expression *funcexpr)
 {
@@ -3348,6 +3369,44 @@ bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value
     return true;
 }
 
+#include <stdio.h>
+bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ir_value *frameval, *thinkval;
+
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (state operation)");
+        return false;
+    }
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
+        return false;
+    }
+    *out = NULL;
+
+    cgen = self->framenum->codegen;
+    if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
+        return false;
+    if (!frameval)
+        return false;
+
+    cgen = self->nextthink->codegen;
+    if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
+        return false;
+    if (!frameval)
+        return false;
+
+    if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
+        compile_error(ast_ctx(self), "failed to create STATE instruction");
+        return false;
+    }
+
+    self->expression.outr = (ir_value*)1;
+    return true;
+}
+
 bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
 {
     ast_expression_codegen *cgen;
diff --git a/ast.h b/ast.h
index 4720b402d4b3d4a9dbe28a209a604155e56db678..d1b250d8abb5cc4a5e2ffc1ebc945e8a13a7d09f 100644 (file)
--- a/ast.h
+++ b/ast.h
@@ -54,6 +54,7 @@ typedef struct ast_switch_s      ast_switch;
 typedef struct ast_label_s       ast_label;
 typedef struct ast_goto_s        ast_goto;
 typedef struct ast_argpipe_s     ast_argpipe;
+typedef struct ast_state_s       ast_state;
 
 enum {
     AST_FLAG_VARIADIC       = 1 << 0,
@@ -111,7 +112,8 @@ enum {
     TYPE_ast_switch,      /* 18 */
     TYPE_ast_label,       /* 19 */
     TYPE_ast_goto,        /* 20 */
-    TYPE_ast_argpipe      /* 21 */
+    TYPE_ast_argpipe,     /* 21 */
+    TYPE_ast_state        /* 22 */
 };
 
 #define ast_istype(x, t) ( ((ast_node*)x)->nodetype == (TYPE_##t) )
@@ -580,6 +582,19 @@ struct ast_goto_s
 ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name);
 void ast_goto_set_label(ast_goto*, ast_label*);
 
+/* STATE node
+ *
+ * For frame/think state updates: void foo() [framenum, nextthink] {}
+ */
+struct ast_state_s
+{
+    ast_expression  expression;
+    ast_expression *framenum;
+    ast_expression *nextthink;
+};
+ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think);
+void ast_state_delete(ast_state*);
+
 /* CALL node
  *
  * Contains an ast_expression as target, rather than an ast_function/value.
index 33a0eff3fbedef27673b700dc9511176e3be2029..50bce0ecf7cd8adae09f787055a909bf71c8137d 100644 (file)
@@ -168,6 +168,11 @@ DEBUG OPTION. Print the code's intermediate representation after the
 optimization and finalization passes to stdout before generating the
 binary. The instructions will be enumerated, and values will contain a
 list of liferanges.
+.It Fl force-crc= Ns Ar CRC
+Force the produced progs file to use the specified CRC.
+.It Fl state-fps= Ns Ar NUM
+Activate \-femulate-state and set the emulated FPS to
+.Ar NUM Ns .
 .El
 .Sh COMPILE WARNINGS
 .Bl -tag -width Ds
@@ -581,6 +586,11 @@ breaks decompilers, but causes the output file to be better compressible.
 In commutative instructions, always put the lower-numbered operand first.
 This shaves off 1 byte of entropy from all these instructions, reducing
 compressed size of the output file.
+.It Fl f Ns Cm emulate-state
+Emulate OP_STATE operations in code rather than using the instruction.
+The desired fps can be set via -state-fps=NUM, defaults to 10.
+Specifying \-state-fps implicitly sets this flag. Defaults to off in all
+standards.
 .It Fl f Ns Cm arithmetic-exceptions
 Turn on arithmetic exception tests in the compiler. In constant expressions
 which trigger exceptions like division by zero, overflow, underflow, etc,
diff --git a/exec.c b/exec.c
index 3c36e11eedbebf44cb3a2d9ef208c5aac4bea75b..9b6677f38305469bf8a9866ae04b6cebc779765b 100644 (file)
--- a/exec.c
+++ b/exec.c
@@ -55,8 +55,16 @@ qc_program_t* prog_load(const char *filename, bool skipversion)
 {
     prog_header_t   header;
     qc_program_t   *prog;
+    size_t          i;
     fs_file_t      *file  = fs_file_open(filename, "rb");
 
+    /* we need all those in order to support INSTR_STATE: */
+    bool            has_self      = false,
+                    has_time      = false,
+                    has_think     = false,
+                    has_nextthink = false,
+                    has_frame     = false;
+
     if (!file)
         return NULL;
 
@@ -137,6 +145,36 @@ qc_program_t* prog_load(const char *filename, bool skipversion)
     memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
     prog->entities = 1;
 
+    /* cache some globals and fields from names */
+    for (i = 0; i < vec_size(prog->defs); ++i) {
+        const char *name = prog_getstring(prog, prog->defs[i].name);
+        if      (!strcmp(name, "self")) {
+            prog->cached_globals.self = prog->defs[i].offset;
+            has_self = true;
+        }
+        else if (!strcmp(name, "time")) {
+            prog->cached_globals.time = prog->defs[i].offset;
+            has_time = true;
+        }
+    }
+    for (i = 0; i < vec_size(prog->fields); ++i) {
+        const char *name = prog_getstring(prog, prog->fields[i].name);
+        if      (!strcmp(name, "think")) {
+            prog->cached_fields.think     = prog->fields[i].offset;
+            has_think = true;
+        }
+        else if (!strcmp(name, "nextthink")) {
+            prog->cached_fields.nextthink = prog->fields[i].offset;
+            has_nextthink = true;
+        }
+        else if (!strcmp(name, "frame")) {
+            prog->cached_fields.frame     = prog->fields[i].offset;
+            has_frame = true;
+        }
+    }
+    if (has_self && has_time && has_think && has_nextthink && has_frame)
+        prog->supports_state = true;
+
     return prog;
 
 error:
@@ -1574,8 +1612,24 @@ while (prog->vmerror == 0) {
             break;
 
         case INSTR_STATE:
-            qcvmerror(prog, "`%s` tried to execute a STATE operation", prog->filename);
+        {
+            qcfloat_t *nextthink;
+            qcfloat_t *time;
+            qcfloat_t *frame;
+            if (!prog->supports_state) {
+                qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]);
+            ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function;
+
+            frame     = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame];
+            *frame    = OPA->_float;
+            nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink];
+            time      = (qcfloat_t*)(prog->globals + prog->cached_globals.time);
+            *nextthink = *time + 0.1;
             break;
+        }
 
         case INSTR_GOTO:
             st += st->o1.s1 - 1;    /* offset the s++ */
diff --git a/gmqcc.h b/gmqcc.h
index 835931df990a5431399f463728053f652321ce72..7834a24fd59216b580d2a92c3ff8e78e105cb4e0 100644 (file)
--- a/gmqcc.h
+++ b/gmqcc.h
@@ -789,17 +789,17 @@ typedef struct {
 } qc_exec_stack_t;
 
 typedef struct qc_program_s {
-    char                    *filename;
+    char                     *filename;
     prog_section_statement_t *code;
     prog_section_def_t       *defs;
     prog_section_def_t       *fields;
     prog_section_function_t  *functions;
-    char                    *strings;
-    qcint_t                   *globals;
-    qcint_t                   *entitydata;
-    bool                    *entitypool;
+    char                     *strings;
+    qcint_t                  *globals;
+    qcint_t                  *entitydata;
+    bool                     *entitypool;
 
-    const char*             *function_stack;
+    const char*              *function_stack;
 
     uint16_t crc16;
 
@@ -825,6 +825,20 @@ typedef struct qc_program_s {
     size_t xflags;
 
     int    argc; /* current arg count for debugging */
+
+    /* cached fields */
+    struct {
+        qcint_t frame;
+        qcint_t nextthink;
+        qcint_t think;
+    } cached_fields;
+
+    struct {
+        qcint_t self;
+        qcint_t time;
+    } cached_globals;
+
+    bool supports_state; /* is INSTR_STATE supported? */
 } qc_program_t;
 
 qc_program_t*       prog_load      (const char *filename, bool ignoreversion);
index f7f391396c9d61dc96f0f4e86a388a4294db9799..ceacf289c3fd606f43c9532171c9161fa8d464b4 100644 (file)
     SORT_OPERANDS = false
 
 
+    #Emulate OP_STATE operations in code rather than using the instruction.
+    #The desired fps can be set via -state-fps=NUM, defaults to 10.
+
+    EMULATE_STATE = false
+
+
     #Turn on arithmetic exception tests in the compiler. In constant expressions
     #which trigger exceptions like division by zero, overflow, underflow, etc,
     #the following flag will produce diagnostics for what triggered that
diff --git a/ir.c b/ir.c
index 7df8bdbff6d7a51b33561a80edb4c96097dc167c..ef782f91e5548c512e17b3054b4e4438cbe7e525 100644 (file)
--- a/ir.c
+++ b/ir.c
@@ -1537,6 +1537,26 @@ bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *t
     return true;
 }
 
+bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    in = ir_instr_new(ctx, self, INSTR_STATE);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, frame, false) ||
+        !ir_instr_op(in, 1, think, false))
+    {
+        ir_instr_delete(in);
+        return false;
+    }
+    vec_push(self->instr, in);
+    return true;
+}
+
 static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
 {
     int op = 0;
@@ -3167,8 +3187,14 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc
         }
 
         if (instr->opcode == INSTR_STATE) {
-            irerror(block->context, "TODO: state instruction");
-            return false;
+            stmt.opcode = instr->opcode;
+            if (instr->_ops[0])
+                stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
+            if (instr->_ops[1])
+                stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o3.u1 = 0;
+            code_push_statement(code, &stmt, instr->context);
+            continue;
         }
 
         stmt.opcode = instr->opcode;
diff --git a/ir.h b/ir.h
index 5b948358fdee732766bf818a66653350460c823a..d0fd7879c5c20509dd29f25eb11588ebb0ea015a 100644 (file)
--- a/ir.h
+++ b/ir.h
@@ -170,6 +170,7 @@ bool GMQCC_WARN ir_block_create_store_op(ir_block*, lex_ctx_t, int op, ir_value
 bool GMQCC_WARN ir_block_create_storep(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
 ir_value*       ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, int outype);
 ir_value*       ir_block_create_fieldaddress(ir_block*, lex_ctx_t, const char *label, ir_value *entity, ir_value *field);
+bool GMQCC_WARN ir_block_create_state_op(ir_block*, lex_ctx_t, ir_value *frame, ir_value *think);
 
 /* This is to create an instruction of the form
  * <outtype>%label := opcode a, b
diff --git a/main.c b/main.c
index 156a3f0c1af5c81c4c72d7ac70dc64087ea5c29c..ff15b20ec329802c1c2167fe76866fccbf92ef22 100644 (file)
--- a/main.c
+++ b/main.c
@@ -85,6 +85,7 @@ static int usage(void) {
             "  -Ono-<name>            disable specific optimization\n"
             "  -Ohelp                 list optimizations\n");
     con_out("  -force-crc=num         force a specific checksum into the header\n");
+    con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
     con_out("  -coverage              add coverage support\n");
     return -1;
 }
@@ -217,6 +218,11 @@ static bool options_parse(int argc, char **argv) {
                 OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0);
                 continue;
             }
+            if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
+                OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0);
+                opts_set(opts.flags, EMULATE_STATE, true);
+                continue;
+            }
             if (options_long_gcc("redirout", &argc, &argv, &redirout)) {
                 con_change(redirout, redirerr);
                 continue;
diff --git a/opts.c b/opts.c
index ddb48f7d00e6f6b27bad2341c4e13fb6594a3506..12429a8ac3e637d7b499ed1c358af9aab917b9c1 100644 (file)
--- a/opts.c
+++ b/opts.c
@@ -102,6 +102,8 @@ static void opts_setdefault(void) {
     opts_set(opts.flags, LEGACY_VECTOR_MATHS,            true);
     opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG,    true);
 
+    /* options */
+    OPTS_OPTION_U32(OPTION_STATE_FPS) = 10;
 }
 
 void opts_backup_non_Wall() {
index 669e734801846565eab654e46f671b6fc19754b8..11571947dbbf96d41cefabbc3f41ed19b12a438d 100644 (file)
--- a/opts.def
+++ b/opts.def
@@ -56,6 +56,7 @@
     GMQCC_DEFINE_FLAG(UNSAFE_VARARGS)
     GMQCC_DEFINE_FLAG(TYPELESS_STORES)
     GMQCC_DEFINE_FLAG(SORT_OPERANDS)
+    GMQCC_DEFINE_FLAG(EMULATE_STATE)
     GMQCC_DEFINE_FLAG(ARITHMETIC_EXCEPTIONS)
 #endif
 
     GMQCC_DEFINE_FLAG(STATISTICS)
     GMQCC_DEFINE_FLAG(PROGSRC)
     GMQCC_DEFINE_FLAG(COVERAGE)
+    GMQCC_DEFINE_FLAG(STATE_FPS)
 #endif
 
 /* some cleanup so we don't have to */
index fc02cac8978ed942b95433c3564f28659956520b..b7d4bf56ae560296063261be7609ce112d43cc04 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -4015,69 +4015,83 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
     }
 
     if (has_frame_think) {
-        lex_ctx_t ctx;
-        ast_expression *self_frame;
-        ast_expression *self_nextthink;
-        ast_expression *self_think;
-        ast_expression *time_plus_1;
-        ast_store *store_frame;
-        ast_store *store_nextthink;
-        ast_store *store_think;
-
-        ctx = parser_ctx(parser);
-        self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
-        self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
-        self_think     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think);
-
-        time_plus_1    = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F,
-                         gbl_time, (ast_expression*)fold_constgen_float(parser->fold, 0.1f, false));
-
-        if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
-            if (self_frame)     ast_delete(self_frame);
-            if (self_nextthink) ast_delete(self_nextthink);
-            if (self_think)     ast_delete(self_think);
-            if (time_plus_1)    ast_delete(time_plus_1);
-            retval = false;
-        }
-
-        if (retval)
-        {
-            store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
-            store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
-            store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
-
-            if (!store_frame) {
-                ast_delete(self_frame);
-                retval = false;
-            }
-            if (!store_nextthink) {
-                ast_delete(self_nextthink);
-                retval = false;
-            }
-            if (!store_think) {
-                ast_delete(self_think);
-                retval = false;
+        if (!OPTS_FLAG(EMULATE_STATE)) {
+            ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink);
+            if (!ast_block_add_expr(block, (ast_expression*)state_op)) {
+                parseerror(parser, "failed to generate state op for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                ast_delete(block);
+                return false;
             }
-            if (!retval) {
-                if (store_frame)     ast_delete(store_frame);
-                if (store_nextthink) ast_delete(store_nextthink);
-                if (store_think)     ast_delete(store_think);
+        } else {
+            /* emulate OP_STATE in code: */
+            lex_ctx_t ctx;
+            ast_expression *self_frame;
+            ast_expression *self_nextthink;
+            ast_expression *self_think;
+            ast_expression *time_plus_1;
+            ast_store *store_frame;
+            ast_store *store_nextthink;
+            ast_store *store_think;
+
+            float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
+
+            ctx = parser_ctx(parser);
+            self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
+            self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
+            self_think     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think);
+
+            time_plus_1    = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F,
+                             gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false));
+
+            if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
+                if (self_frame)     ast_delete(self_frame);
+                if (self_nextthink) ast_delete(self_nextthink);
+                if (self_think)     ast_delete(self_think);
+                if (time_plus_1)    ast_delete(time_plus_1);
                 retval = false;
             }
-            if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
-                !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
-                !ast_block_add_expr(block, (ast_expression*)store_think))
+
+            if (retval)
             {
-                retval = false;
+                store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
+                store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
+                store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
+
+                if (!store_frame) {
+                    ast_delete(self_frame);
+                    retval = false;
+                }
+                if (!store_nextthink) {
+                    ast_delete(self_nextthink);
+                    retval = false;
+                }
+                if (!store_think) {
+                    ast_delete(self_think);
+                    retval = false;
+                }
+                if (!retval) {
+                    if (store_frame)     ast_delete(store_frame);
+                    if (store_nextthink) ast_delete(store_nextthink);
+                    if (store_think)     ast_delete(store_think);
+                    retval = false;
+                }
+                if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
+                    !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
+                    !ast_block_add_expr(block, (ast_expression*)store_think))
+                {
+                    retval = false;
+                }
             }
-        }
 
-        if (!retval) {
-            parseerror(parser, "failed to generate code for [frame,think]");
-            ast_unref(nextthink);
-            ast_unref(framenum);
-            ast_delete(block);
-            return false;
+            if (!retval) {
+                parseerror(parser, "failed to generate code for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                ast_delete(block);
+                return false;
+            }
         }
     }
 
diff --git a/test.c b/test.c
index 29f4b1100fb1ea0df4b0bbea4f2eaef206207460..23fbfb6b4ef8ed0d5f8c9e37446ddd9b1c9b775e 100644 (file)
--- a/test.c
+++ b/test.c
@@ -85,7 +85,7 @@ static fs_file_t **task_popen(const char *command, const char *mode) {
             while (*line != '\0' && *line != ' ' &&
                    *line != '\t' && *line != '\n') line++;
         }
-        vec_push(argv, '\0');
+        vec_push(argv, NULL);
     }
 
 
diff --git a/tests/state-emu.tmpl b/tests/state-emu.tmpl
new file mode 100644 (file)
index 0000000..1d96adc
--- /dev/null
@@ -0,0 +1,9 @@
+I: state.qc
+D: test emulated state ops
+T: -execute
+C: -std=gmqcc -femulate-state
+M: st1, .frame=1, .nextthink=10.1 (now: 10)
+M: st2, .frame=2, .nextthink=11.1 (now: 11)
+M: st3, .frame=0, .nextthink=12.1 (now: 12)
+M: st1, .frame=1, .nextthink=13.1 (now: 13)
+M: st2, .frame=2, .nextthink=14.1 (now: 14)
diff --git a/tests/state.qc b/tests/state.qc
new file mode 100644 (file)
index 0000000..9f99ccc
--- /dev/null
@@ -0,0 +1,39 @@
+float  time;
+entity self;
+
+.void() think;
+.float  nextthink;
+.float  frame;
+
+void stprint(string fun) {
+    print(fun,
+          ", .frame=", ftos(self.frame),
+          ", .nextthink=", ftos(self.nextthink),
+          " (now: ", ftos(time), ")\n");
+}
+
+void st1() = [1, st2] { stprint("st1"); }
+void st2() = [2, st3] { stprint("st2"); }
+void st3() = [0, st1] { stprint("st3"); }
+
+void main() {
+    entity ea = spawn();
+    entity eb = spawn();
+
+    time = 10;
+    self = ea;
+
+    self.think     = st1;
+    self.nextthink = time;
+    self.frame     = 100;
+
+    self.think();
+    time = 11;
+    self.think();
+    time = 12;
+    self.think();
+    time = 13;
+    self.think();
+    time = 14;
+    self.think();
+};
diff --git a/tests/state.tmpl b/tests/state.tmpl
new file mode 100644 (file)
index 0000000..6e9cf67
--- /dev/null
@@ -0,0 +1,9 @@
+I: state.qc
+D: test state ops
+T: -execute
+C: -std=gmqcc
+M: st1, .frame=1, .nextthink=10.1 (now: 10)
+M: st2, .frame=2, .nextthink=11.1 (now: 11)
+M: st3, .frame=0, .nextthink=12.1 (now: 12)
+M: st1, .frame=1, .nextthink=13.1 (now: 13)
+M: st2, .frame=2, .nextthink=14.1 (now: 14)