summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2022-07-31 16:02:10 +0100
committerMichael Smith <mikesmiffy128@gmail.com>2022-08-10 22:40:52 +0100
commit5e921bf59373d79d27c322ff86e8b5a37b151e45 (patch)
tree4d40e39543b085ce4c8cb9b1a7b3c0108de680c0
parentc8d7588251fd4fe63ac6afe2a90ca7066c786609 (diff)
Add magical feature codegen system, at long last
-rw-r--r--TODO/.featgen3
-rw-r--r--TODO/featgen9
-rw-r--r--src/ac.c48
-rw-r--r--src/ac.h5
-rw-r--r--src/alias.c44
-rw-r--r--src/alias.h6
-rw-r--r--src/autojump.c20
-rw-r--r--src/autojump.h27
-rw-r--r--src/bind.c5
-rw-r--r--src/bind.h4
-rw-r--r--src/build/cmeta.c83
-rw-r--r--src/build/cmeta.h33
-rw-r--r--src/build/codegen.c326
-rw-r--r--src/democustom.c13
-rw-r--r--src/democustom.h4
-rw-r--r--src/demorec.c14
-rw-r--r--src/demorec.h5
-rw-r--r--src/engineapi.c4
-rw-r--r--src/ent.c5
-rw-r--r--src/ent.h5
-rw-r--r--src/event.h2
-rw-r--r--src/feature.h94
-rw-r--r--src/fov.c25
-rw-r--r--src/hook.c2
-rw-r--r--src/l4dwarp.c18
-rw-r--r--src/l4dwarp.h26
-rw-r--r--src/nosleep.c27
-rw-r--r--src/nosleep.h27
-rw-r--r--src/portalcolours.c14
-rw-r--r--src/portalcolours.h27
-rw-r--r--src/rinput.c11
-rw-r--r--src/rinput.h29
-rw-r--r--src/sst.c70
-rw-r--r--src/sst.h5
34 files changed, 664 insertions, 376 deletions
diff --git a/TODO/.featgen b/TODO/.featgen
new file mode 100644
index 0000000..bf6dc38
--- /dev/null
+++ b/TODO/.featgen
@@ -0,0 +1,3 @@
+Feature setup code generation
+====
+It's done! See feature.h, event.h and the various stuff in codegen.c.
diff --git a/TODO/featgen b/TODO/featgen
deleted file mode 100644
index b71f32a..0000000
--- a/TODO/featgen
+++ /dev/null
@@ -1,9 +0,0 @@
-Feature setup code generation
-====
-Once the plugin has enough features across enough C files, setting up and
-tearing down everything will get kind of annoying. That's where some more code
-generation might be nice.
-
-There's a few vague ideas of how to do this but really it's pretty open-ended
-overall. In the meantime, we're just calling functions manually. Not a huge
-deal, really.
diff --git a/src/ac.c b/src/ac.c
index 2a1a8a4..4d21a48 100644
--- a/src/ac.c
+++ b/src/ac.c
@@ -23,14 +23,23 @@
#include "hook.h"
#include "engineapi.h"
#include "errmsg.h"
+#include "event.h"
+#include "feature.h"
#include "intdefs.h"
#include "mem.h"
#include "os.h"
#include "ppmagic.h"
+#include "sst.h"
#include "vcall.h"
#include "x86.h"
#include "x86util.h"
+FEATURE()
+REQUIRE(bind)
+REQUIRE(democustom)
+REQUIRE_GAMEDATA(vtidx_GetDesktopResolution)
+REQUIRE_GAMEDATA(vtidx_DispatchAllStoredGameMessages)
+
static bool lockdown = false;
#ifdef _WIN32
@@ -62,18 +71,10 @@ static ssize __stdcall mproc(int code, UINT_PTR wp, ssize lp) {
static ulong __stdcall inhookthrmain(void *unused) {
if (!SetWindowsHookExW(WH_KEYBOARD_LL, &kproc, 0, 0) ||
!SetWindowsHookExW(WH_MOUSE_LL, &mproc, 0, 0)) {
- // intentionally vague message
- con_warn("sst: RTA mode is unavailable due to an error\n");
return -1;
}
MSG m; int ret;
while ((ret = GetMessageW(&m, inhookwin, 0, 0)) > 0) DispatchMessage(&m);
- if (ret == -1) {
- // XXX: if this ever happens, it's a disaster! users might not notice
- // their run just dying all of a sudden. with any luck it won't matter
- // in practice but... this kind of sucks.
- con_warn("** sst: ERROR in message loop, abandoning RTA mode! **");
- }
return ret;
}
@@ -92,12 +93,15 @@ static void inhook_start(void) {
inhookthr = CreateThread(0, 0, &inhookthrmain, 0, 0, &inhooktid);
}
-// TODO(rta): run this check every tick (or at least X amount of time)
static void inhook_check(void) {
if (WaitForSingleObject(inhookthr, 0) == WAIT_OBJECT_0) {
ulong status;
GetExitCodeThread(inhookthr, &status);
- if (status != 0) {
+ if (status) {
+ // XXX: if this ever happens, it's a disaster! users might not
+ // notice their run just dying all of a sudden. with any luck it
+ // won't matter in practice but... this kind of sucks.
+ con_warn("** sst: ERROR in message loop, abandoning RTA mode! **");
// TODO(rta): stop demos, and stuff.
lockdown = false;
}
@@ -110,6 +114,13 @@ static void inhook_stop(void) {
errmsg_warnsys("couldn't wait for thread, status unknown");
// XXX: now what!?
}
+ // assume WAIT_OBJECT_0
+ ulong status;
+ GetExitCodeThread(inhookthr, &status);
+ if (status) {
+ // not much else we can do now!
+ con_warn("warning: RTA mode message loop had an error during shutdown");
+ }
}
#else
@@ -131,6 +142,14 @@ static void startlockdown(void) {
// TODO(rta): start demos, etc
}
+HANDLE_EVENT(Tick) {
+#ifdef _WIN32
+ static int fewticks = 0;
+ // just check this every so often (roughly 0.1-0.3s depending on game)
+ if (lockdown && !(++fewticks & 7)) inhook_check();
+#endif
+}
+
static void endlockdown(void) {
if (!lockdown) return;
#ifdef _WIN32
@@ -227,12 +246,7 @@ ok: DispatchAllStoredGameMessages_func DispatchAllStoredGameMessages =
return false;
}
-bool ac_init(void) {
- if (!has_vtidx_GetDesktopResolution ||
- !has_vtidx_DispatchAllStoredGameMessages) {
- errmsg_errorx("missing gamedata entries for this engine");
- return false;
- }
+INIT {
#if defined(_WIN32)
if (!win32_init()) return false;
#elif defined(__linux__)
@@ -248,7 +262,7 @@ bool ac_init(void) {
return true;
}
-void ac_end(void) {
+END {
endlockdown();
unhook_inline((void *)orig_DispatchInputEvent);
}
diff --git a/src/ac.h b/src/ac.h
index 638b01f..7b48cd1 100644
--- a/src/ac.h
+++ b/src/ac.h
@@ -17,10 +17,7 @@
#ifndef INC_AC_H
#define INC_AC_H
-#include <stdbool.h>
-
-bool ac_init(void);
-void ac_end(void);
+// TODO(rta): keeping this header here as I expect to expose some functions...
#endif
diff --git a/src/alias.c b/src/alias.c
index 3605800..b94a6d5 100644
--- a/src/alias.c
+++ b/src/alias.c
@@ -19,16 +19,27 @@
#include "alias.h"
#include "con_.h"
-#include "dbg.h"
#include "errmsg.h"
#include "extmalloc.h"
+#include "feature.h"
#include "gametype.h"
#include "mem.h"
#include "x86.h"
#include "x86util.h"
+FEATURE("alias management")
+
struct alias **_alias_head;
+void alias_nuke(void) {
+ for (struct alias *p = alias_head; p;) {
+ struct alias *next = p->next;
+ extfree(p->value); extfree(p);
+ p = next;
+ }
+ alias_head = 0;
+}
+
void alias_rm(const char *name) {
for (struct alias **p = _alias_head; *p; p = &(*p)->next) {
if (!strcmp((*p)->name, name)) {
@@ -40,7 +51,15 @@ void alias_rm(const char *name) {
}
}
-DEF_CCMD_HERE(sst_alias_remove, "Remove a command alias", 0) {
+DEF_CCMD_HERE_UNREG(sst_alias_clear, "Remove all command aliases", 0) {
+ if (cmd->argc != 1) {
+ con_warn("usage: sst_alias_clear");
+ return;
+ }
+ alias_nuke();
+}
+
+DEF_CCMD_HERE_UNREG(sst_alias_remove, "Remove a command alias", 0) {
if (cmd->argc != 2) {
con_warn("usage: sst_alias_remove name");
return;
@@ -52,23 +71,6 @@ DEF_CCMD_HERE(sst_alias_remove, "Remove a command alias", 0) {
alias_rm(cmd->argv[1]);
}
-void alias_nuke(void) {
- for (struct alias *p = alias_head; p;) {
- struct alias *next = p->next;
- extfree(p->value); extfree(p);
- p = next;
- }
- alias_head = 0;
-}
-
-DEF_CCMD_HERE(sst_alias_clear, "Remove all command aliases", 0) {
- if (cmd->argc != 1) {
- con_warn("usage: sst_alias_clear");
- return;
- }
- alias_nuke();
-}
-
static bool find_alias_head(con_cmdcb alias_cb) {
#ifdef _WIN32
for (uchar *p = (uchar *)alias_cb; p - (uchar *)alias_cb < 64;) {
@@ -88,7 +90,7 @@ static bool find_alias_head(con_cmdcb alias_cb) {
return false;
}
-bool alias_init(void) {
+INIT {
// TODO(compat): no idea why sst_alias_clear crashes in p2, figure out later
if (GAMETYPE_MATCHES(Portal2)) return false;
@@ -101,6 +103,8 @@ bool alias_init(void) {
errmsg_warnx("couldn't find alias list");
return false;
};
+ con_reg(sst_alias_clear);
+ con_reg(sst_alias_remove);
return true;
}
diff --git a/src/alias.h b/src/alias.h
index 3e417d9..bc125eb 100644
--- a/src/alias.h
+++ b/src/alias.h
@@ -17,8 +17,6 @@
#ifndef INC_ALIAS_H
#define INC_ALIAS_H
-#include <stdbool.h>
-
struct alias {
struct alias *next;
char name[32]; // TIL this has a hard limit :^)
@@ -27,10 +25,8 @@ struct alias {
extern struct alias **_alias_head;
#define alias_head (*_alias_head) // act as a global
-void alias_rm(const char *name);
void alias_nuke(void);
-
-bool alias_init(void);
+void alias_rm(const char *name);
#endif
diff --git a/src/autojump.c b/src/autojump.c
index 284dd85..c4f54f4 100644
--- a/src/autojump.c
+++ b/src/autojump.c
@@ -19,6 +19,7 @@
#include "con_.h"
#include "engineapi.h"
#include "errmsg.h"
+#include "feature.h"
#include "gamedata.h"
#include "gametype.h"
#include "intdefs.h"
@@ -27,6 +28,11 @@
#include "os.h"
#include "vcall.h"
+FEATURE("autojump")
+REQUIRE_GAMEDATA(off_mv)
+REQUIRE_GAMEDATA(vtidx_CheckJumpButton)
+REQUIRE_GLOBAL(factory_client) // note: server will never be null
+
DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0,
CON_REPLICATE | CON_DEMO | CON_HIDDEN)
@@ -69,17 +75,7 @@ static bool unprot(void *gm) {
return ret;
}
-bool autojump_init(void) {
- // TODO(featgen): auto-check these factories
- if (!factory_client || !factory_server) {
- errmsg_errorx("missing required factories");
- return false;
- }
- if (!has_vtidx_CheckJumpButton || !has_off_mv) {
- errmsg_errorx("missing gamedata entries for this engine");
- return false;
- }
-
+INIT {
gmsv = factory_server("GameMovement001", 0);
if (!gmsv) {
errmsg_errorx("couldn't get server-side game movement interface");
@@ -111,7 +107,7 @@ bool autojump_init(void) {
return true;
}
-void autojump_end(void) {
+END {
unhook_vtable(*(void ***)gmsv, vtidx_CheckJumpButton, (void *)origsv);
unhook_vtable(*(void ***)gmcl, vtidx_CheckJumpButton, (void *)origcl);
}
diff --git a/src/autojump.h b/src/autojump.h
deleted file mode 100644
index 56377b7..0000000
--- a/src/autojump.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef INC_AUTOJUMP_H
-#define INC_AUTOJUMP_H
-
-#include <stdbool.h>
-
-bool autojump_init(void);
-void autojump_end(void);
-
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/bind.c b/src/bind.c
index 62cdec3..762259d 100644
--- a/src/bind.c
+++ b/src/bind.c
@@ -19,12 +19,15 @@
#include "con_.h"
#include "dbg.h"
#include "errmsg.h"
+#include "feature.h"
#include "hook.h"
#include "intdefs.h"
#include "mem.h"
#include "x86.h"
#include "x86util.h"
+FEATURE()
+
struct keyinfo {
char *binding;
uchar keyuptgt : 3;
@@ -53,7 +56,7 @@ static bool find_keyinfo(con_cmdcb klbc_cb) {
return false;
}
-bool bind_init(void) {
+INIT {
struct con_cmd *cmd_key_listboundkeys = con_findcmd("key_listboundkeys");
if (!cmd_key_listboundkeys) {
errmsg_errorx("couldn't find key_listboundkeys command");
diff --git a/src/bind.h b/src/bind.h
index 4d91a96..daf97da 100644
--- a/src/bind.h
+++ b/src/bind.h
@@ -17,12 +17,8 @@
#ifndef INC_BIND_H
#define INC_BIND_H
-#include <stdbool.h>
-
const char *bind_get(int keycode);
-bool bind_init(void);
-
#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/build/cmeta.c b/src/build/cmeta.c
index 3d49927..260d33f 100644
--- a/src/build/cmeta.c
+++ b/src/build/cmeta.c
@@ -20,6 +20,7 @@
#include "../intdefs.h"
#include "../os.h"
+#include "cmeta.h"
/*
* This file does C metadata parsing/scraping for the build system. This
@@ -31,10 +32,12 @@
* time. Don't worry about it too much.
*/
-// too lazy to write a C tokenizer at the moment, so let's just yoink some code
-// from a hacked-up copy of chibicc, a nice minimal C compiler with code that's
-// pretty easy to work with. it does leak memory by design, but build stuff is
-// all one-shot so that's fine.
+// lazy inlined 3rd party stuff {{{
+// too lazy to write a C tokenizer at the moment, or indeed probably ever, so
+// let's just yoink some code from a hacked-up copy of chibicc, a nice minimal C
+// compiler with code that's pretty easy to work with. it does leak memory by
+// design, but build stuff is all one-shot so that's fine.
+#include "../3p/chibicc/chibicc.h"
#include "../3p/chibicc/unicode.c"
// type sentinels from type.c (don't bring in the rest of type.c because it
// circularly depends on other stuff and we really only want tokenize here)
@@ -88,6 +91,7 @@ static char *join_tokens(Token *tok, Token *end) {
buf[pos] = '\0';
return buf;
}
+// }}}
#ifdef _WIN32
#include "../3p/openbsd/asprintf.c" // missing from libc; plonked here for now
@@ -266,7 +270,74 @@ void cmeta_conmacros(const struct cmeta *cm,
}
}
-void cmeta_evdefmacros(const struct cmeta *cm, void (*cb_def)(const char *name)) {
+const char *cmeta_findfeatmacro(const struct cmeta *cm) {
+ Token *tp = (Token *)cm;
+ if (!tp || !tp->next) return 0; // FEATURE, (
+ while (tp) {
+ if (equal(tp, "FEATURE") && equal(tp->next, "(")) {
+ if (equal(tp->next->next, ")")) return ""; // no arg = no desc
+ if (!tp->next->next || tp->next->next->kind != TK_STR) {
+ return 0; // it's invalid, whatever, just return...
+ }
+ return tp->next->next->str;
+ }
+ tp = tp->next;
+ }
+ return 0;
+}
+
+void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)(
+ enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt) {
+ Token *tp = (Token *)cm;
+ if (!tp || !tp->next) return;
+ while (tp) {
+ int type = -1;
+ if (equal(tp, "PREINIT")) {
+ type = CMETA_FEAT_PREINIT;
+ }
+ else if (equal(tp, "INIT")) {
+ type = CMETA_FEAT_INIT;
+ }
+ else if (equal(tp, "END")) {
+ type = CMETA_FEAT_END;
+ }
+ if (type != - 1) {
+ if (equal(tp->next, "{")) {
+ cb(type, 0, ctxt);
+ tp = tp->next;
+ }
+ tp = tp->next;
+ continue;
+ }
+ if (equal(tp, "REQUIRE")) {
+ type = CMETA_FEAT_REQUIRE;
+ }
+ else if (equal(tp, "REQUIRE_GAMEDATA")) {
+ type = CMETA_FEAT_REQUIREGD;
+ }
+ else if (equal(tp, "REQUIRE_GLOBAL")) {
+ type = CMETA_FEAT_REQUIREGLOBAL;
+ }
+ else if (equal(tp, "REQUEST")) {
+ type = CMETA_FEAT_REQUEST;
+ }
+ if (type != -1) {
+ if (equal(tp->next, "(") && tp->next->next) {
+ tp = tp->next->next;
+ char *param = malloc(tp->len + 1);
+ if (!param) die1("couldn't allocate memory");
+ memcpy(param, tp->loc, tp->len);
+ param[tp->len] = '\0';
+ cb(type, param, ctxt);
+ tp = tp->next;
+ }
+ }
+ tp = tp->next;
+ }
+}
+
+void cmeta_evdefmacros(const struct cmeta *cm,
+ void (*cb_def)(const char *name)) {
Token *tp = (Token *)cm;
if (!tp || !tp->next || !tp->next->next) return; // DEF_EVENT, (, name
while (tp) {
@@ -298,4 +369,4 @@ void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname,
}
}
-// vi: sw=4 ts=4 noet tw=80 cc=80
+// vi: sw=4 ts=4 noet tw=80 cc=80 fdm=marker
diff --git a/src/build/cmeta.h b/src/build/cmeta.h
index 125ce2c..40c4ac5 100644
--- a/src/build/cmeta.h
+++ b/src/build/cmeta.h
@@ -40,6 +40,39 @@ void cmeta_conmacros(const struct cmeta *cm,
void (*cb)(const char *name, bool isvar, bool unreg));
/*
+ * Looks for a feature description macro in file, returning the description
+ * string if it exists, an empty string if the feature is defined without a
+ * user-facing description, and null if source file does not define a feature.
+ */
+const char *cmeta_findfeatmacro(const struct cmeta *cm);
+
+/*
+ * the various kinds of feature specficiation macros, besides the feature
+ * declaration macro itself
+ */
+enum cmeta_featmacro {
+ CMETA_FEAT_REQUIRE,
+ CMETA_FEAT_REQUIREGD,
+ CMETA_FEAT_REQUIREGLOBAL,
+ CMETA_FEAT_REQUEST,
+ CMETA_FEAT_PREINIT,
+ CMETA_FEAT_INIT,
+ CMETA_FEAT_END
+};
+
+/*
+ * Iterates through all feature dependency macros and init/end/preinit
+ * indicators, passing each bit of information to the callback cb.
+ *
+ * PREINT, INIT and END macros don't pass anything to param.
+ *
+ * This one takes a context pointer, while the others don't, because this is all
+ * cobbled together without much consistent abstraction.
+ */
+void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)(
+ enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt);
+
+/*
* Iterates through all event-related macros and takes note of which events are
* defined, giving a callback for each.
*/
diff --git a/src/build/codegen.c b/src/build/codegen.c
index 1dee6a1..04a9058 100644
--- a/src/build/codegen.c
+++ b/src/build/codegen.c
@@ -14,35 +14,37 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "../intdefs.h"
#include "../os.h"
#include "cmeta.h"
#include "skiplist.h"
#include "vec.h"
-#define MAXENT 65536 // arbitrary limit!
-static struct ent {
- const char *name;
- bool unreg;
- bool isvar; // false for cmd
-} ents[MAXENT];
-static int nents;
-
static void die(const char *s) {
fprintf(stderr, "codegen: %s\n", s);
exit(100);
}
+#define MAXENT 65536 // arbitrary limit!
+static struct conent {
+ const char *name;
+ bool unreg;
+ bool isvar; // false for cmd
+} conents[MAXENT];
+static int nconents;
+
#define PUT(name_, isvar_, unreg_) do { \
- if (nents == sizeof(ents) / sizeof(*ents)) { \
+ if (nconents == sizeof(conents) / sizeof(*conents)) { \
fprintf(stderr, "codegen: out of space; make ents bigger!\n"); \
exit(1); \
} \
- ents[nents].name = name_; \
- ents[nents].isvar = isvar_; ents[nents++].unreg = unreg_; \
+ conents[nconents].name = name_; \
+ conents[nconents].isvar = isvar_; conents[nconents++].unreg = unreg_; \
} while (0)
static void oncondef(const char *name, bool isvar, bool unreg) {
@@ -50,10 +52,95 @@ static void oncondef(const char *name, bool isvar, bool unreg) {
}
struct vec_str VEC(const char *);
+struct vec_usize VEC(usize);
+struct vec_featp VEC(struct feature *);
+
+enum { UNSEEN, SEEING, SEEN };
+
+DECL_SKIPLIST(static, feature, struct feature, const char *, 4)
+DECL_SKIPLIST(static, feature_bydesc, struct feature, const char *, 4)
+struct feature {
+ const char *modname;
+ const char *desc;
+ const struct cmeta *cm; // backref for subsequent options pass
+ struct vec_featp needs;
+ // keep optionals in a separate array mainly so we have separate counts
+ struct vec_featp wants;
+ uint dfsstate : 2; // used for sorting and cycle checking
+ bool has_preinit : 1, /*has_init : 1, <- required anyway! */ has_end : 1;
+ bool has_evhandlers : 1;
+ bool is_requested : 1; // determines if has_ variable needs to be extern
+ //char pad : 2;
+ //char pad[3];
+ struct vec_str need_gamedata;
+ struct vec_str need_globals;
+ struct skiplist_hdr_feature hdr; // by id/modname
+ struct skiplist_hdr_feature_bydesc hdr_bydesc;
+};
+static inline int cmp_feature(struct feature *e, const char *s) {
+ return strcmp(e->modname, s);
+}
+static inline int cmp_feature_bydesc(struct feature *e, const char *s) {
+ for (const char *p = e->desc; ; ++p, ++s) {
+ // longer string first
+ if (!*s) return !!*p; else if (!*p) return -1;
+ // case insensitive sort where possible
+ if (tolower(*p) > tolower(*s)) return 1;
+ if (tolower(*p) < tolower(*s)) return -1;
+ // prioritise upper-case if same letter
+ if (isupper(*p) && islower(*s)) return 1;
+ if (islower(*p) && isupper(*s)) return -1;
+ }
+ return 0;
+}
+static inline struct skiplist_hdr_feature *hdr_feature(struct feature *e) {
+ return &e->hdr;
+}
+static inline struct skiplist_hdr_feature_bydesc *hdr_feature_bydesc(
+ struct feature *e) {
+ return &e->hdr_bydesc;
+}
+DEF_SKIPLIST(static, feature, cmp_feature, hdr_feature)
+DEF_SKIPLIST(static, feature_bydesc, cmp_feature_bydesc, hdr_feature_bydesc)
+static struct skiplist_hdr_feature features = {0};
+// sort in two different ways, so we can alphabetise the user-facing display
+// NOTE: not all features will show up in this second list!
+static struct skiplist_hdr_feature_bydesc features_bydesc = {0};
+
+static void onfeatinfo(enum cmeta_featmacro type, const char *param,
+ void *ctxt) {
+ struct feature *f = ctxt;
+ switch (type) {
+ case CMETA_FEAT_REQUIRE:; bool optional = false; goto dep;
+ case CMETA_FEAT_REQUEST: optional = true;
+dep: struct feature *dep = skiplist_get_feature(&features, param);
+ if (optional) dep->is_requested = true;
+ if (!dep) {
+ fprintf(stderr, "codegen: error: feature `%s` tried to depend "
+ "on non-existent feature `%s`\n", f->modname, param);
+ exit(1); \
+ }
+ if (!vec_push(optional ? &f->wants : &f->needs, dep)) {
+ die("couldn't allocate memory");
+ }
+ break;
+ case CMETA_FEAT_REQUIREGD:;
+ struct vec_str *vecp = &f->need_gamedata;
+ goto push;
+ case CMETA_FEAT_REQUIREGLOBAL:
+ vecp = &f->need_globals;
+push: if (!vec_push(vecp, param)) die("couldn't allocate memory");
+ break;
+ case CMETA_FEAT_PREINIT: f->has_preinit = true; break;
+ case CMETA_FEAT_END: f->has_end = true; break;
+ case CMETA_FEAT_INIT:; // nop for now, I guess
+ }
+}
+
DECL_SKIPLIST(static, event, struct event, const char *, 4)
-struct event {
+ struct event {
const char *name;
- struct vec_str handlers;
+ struct vec_usize handlers; // strings, but with tagged pointers - see below
struct skiplist_hdr_event hdr;
};
static inline int cmp_event(struct event *e, const char *s) {
@@ -63,7 +150,7 @@ static inline struct skiplist_hdr_event *hdr_event(struct event *e) {
return &e->hdr;
}
DEF_SKIPLIST(static, event, cmp_event, hdr_event)
-struct skiplist_hdr_event events = {0};
+static struct skiplist_hdr_event events = {0};
static void onevdef(const char *name) {
struct event *e = skiplist_get_event(&events, name);
@@ -71,7 +158,7 @@ static void onevdef(const char *name) {
struct event *e = malloc(sizeof(*e));
if (!e) die("couldn't allocate memory");
e->name = name;
- e->handlers = (struct vec_str){0};
+ e->handlers = (struct vec_usize){0};
e->hdr = (struct skiplist_hdr_event){0};
skiplist_insert_event(&events, name, e);
}
@@ -89,24 +176,95 @@ static void onevhandler(const char *evname, const char *modname) {
"non-existent event `%s`\n", modname, evname);
exit(2);
}
+ usize taggedptr = (usize)modname;
+ struct feature *f = skiplist_get_feature(&features, modname);
+ // hack: using unused pointer bit to determine whether a handler is tied to
+ // a feature and thus conditional. relies on malloc alignment!
+ if (f) taggedptr |= 1ull;
// NOTE: not bothering to check for more than one handler in a file.
// compiler will get that anyway.
- if (!vec_push(&e->handlers, modname)) die("couldn't allocate memory");
+ if (!vec_push(&e->handlers, taggedptr)) die("couldn't allocate memory");
}
+struct passinfo {
+ const struct cmeta *cm;
+ const os_char *path;
+};
+static struct vec_passinfo VEC(struct passinfo) pass2 = {0};
+
#define _(x) \
if (fprintf(out, "%s\n", x) < 0) die("couldn't write to file");
#define F(f, ...) \
if (fprintf(out, f "\n", __VA_ARGS__) < 0) die("couldn't write to file");
-#define H() \
-_( "/* This file is autogenerated by src/build/codegen.c. DO NOT EDIT! */") \
-_( "")
+#define H_() \
+ _( "/* This file is autogenerated by "__FILE__". DO NOT EDIT! */")
+#define H() H_() _( "")
-struct passinfo {
- const struct cmeta *cm;
- const os_char *path;
-};
-struct vec_passinfo VEC(struct passinfo) pass2 = {0};
+static struct vec_featp endstack = {0}; // stack for reversing order
+
+static void featdfs(FILE *out, struct feature *f) {
+ if (f->dfsstate == SEEN) return;
+ if (f->dfsstate == SEEING) {
+ // XXX: could unwind for full cycle listing like in build.
+ // purely being lazy by not doing that here, and assuming there won't
+ // actually be cycles anyway, because this is not a general purpose tool
+ // and people working on this codebase are very smart.
+ fprintf(stderr, "codegen: error: dependency cycle found at feature `%s`\n",
+ f->modname);
+ exit(2);
+ }
+ f->dfsstate = SEEING;
+ // easier to do wants first, then we can do the conditional counter nonsense
+ // without worrying about how that fits in...
+ for (struct feature *const *pp = f->wants.data;
+ pp - f->wants.data < f->wants.sz; ++pp) {
+ featdfs(out, *pp);
+ }
+F( " char status_%s = FEAT_OK;", f->modname);
+ const char *else_ = "";
+ if (f->needs.sz == 1) {
+ featdfs(out, f->needs.data[0]);
+F( " if (status_%s != FEAT_OK) status_%s = FEAT_REQFAIL;",
+ f->needs.data[0]->modname, f->modname)
+ else_ = "else ";
+ }
+ else if (f->needs.sz > 1) {
+ for (struct feature *const *pp = f->needs.data;
+ pp - f->needs.data < f->needs.sz; ++pp) {
+ featdfs(out, *pp);
+ }
+F( " bool metdeps_%s =", f->modname)
+ for (struct feature *const *pp = f->needs.data;
+ pp - f->needs.data < f->needs.sz; ++pp) {
+F( " status_%s == FEAT_OK%s", (*pp)->modname,
+ pp - f->needs.data == f->needs.sz - 1 ? ";" : " &&") // dumb but oh well
+ }
+F( " if (!metdeps_%s) status_%s = FEAT_REQFAIL;", f->modname, f->modname)
+ else_ = "else ";
+ }
+ if (f->has_preinit) {
+F( " %sif (!_feature_preinit_%s()) status_%s = FEAT_PREFAIL;", else_,
+ f->modname, f->modname);
+ else_ = "else ";
+ }
+ for (const char **pp = f->need_gamedata.data;
+ pp - f->need_gamedata.data < f->need_gamedata.sz; ++pp) {
+F( " %sif (!has_%s) status_%s = FEAT_NOGD;", else_, *pp, f->modname)
+ else_ = "else "; // blegh
+ }
+ for (const char **pp = f->need_globals.data;
+ pp - f->need_globals.data < f->need_globals.sz; ++pp) {
+F( " %sif (!%s) status_%s = FEAT_NOGLOBAL;", else_, *pp, f->modname)
+ else_ = "else "; // blegh 2
+ }
+F( " %sif (!_feature_init_%s()) status_%s = FEAT_FAIL;", else_, f->modname,
+ f->modname)
+ if (f->has_end || f->has_evhandlers || f->is_requested) {
+F( " has_%s = status_%s == FEAT_OK;", f->modname, f->modname)
+ }
+ if (!vec_push(&endstack, f)) die("couldn't allocate memory");
+ f->dfsstate = SEEN;
+}
int OS_MAIN(int argc, os_char *argv[]) {
for (++argv; *argv; ++argv) {
@@ -118,8 +276,8 @@ int OS_MAIN(int argc, os_char *argv[]) {
}
}
- // we have to a second pass for event handlers. also, there's a bunch of
- // terrible garbage here. don't stare for too long...
+ // we have to do a second pass for features and event handlers. also,
+ // there's a bunch of terrible garbage here. don't stare for too long...
for (struct passinfo *pi = pass2.data; pi - pass2.data < pass2.sz; ++pi) {
// XXX: I guess we should cache these by name or something!
const struct cmeta *cm = pi->cm;
@@ -162,18 +320,36 @@ int OS_MAIN(int argc, os_char *argv[]) {
else if (!strcmp(modname, "sst")) {
continue; // I guess???
}
+ const char *featdesc = cmeta_findfeatmacro(cm);
+ if (featdesc) {
+ struct feature *f = malloc(sizeof(*f));
+ if (!f) die("couldn't allocate memory");
+ *f = (struct feature){
+ .modname = modname,
+ .desc = featdesc[0] ? featdesc : 0,
+ .cm = cm
+ };
+ skiplist_insert_feature(&features, modname, f);
+ if (f->desc) {
+ skiplist_insert_feature_bydesc(&features_bydesc, f->desc, f);
+ }
+ }
cmeta_evhandlermacros(cm, modname, &onevhandler);
}
+ // yet another pass because I am stupid and don't want to think harder :)
+ for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) {
+ cmeta_featinfomacros(f->cm, &onfeatinfo, f);
+ }
FILE *out = fopen(".build/include/cmdinit.gen.h", "wb");
if (!out) die("couldn't open cmdinit.gen.h");
- H();
- for (const struct ent *p = ents; p - ents < nents; ++p) {
+H();
+ for (const struct conent *p = conents; p - conents < nconents; ++p) {
F( "extern struct con_%s *%s;", p->isvar ? "var" : "cmd", p->name)
}
_( "")
_( "static void regcmds(void) {")
- for (const struct ent *p = ents; p - ents < nents; ++p) {
+ for (const struct conent *p = conents; p - conents < nconents; ++p) {
if (p->isvar) {
F( " initval(%s);", p->name)
}
@@ -184,23 +360,103 @@ F( " con_reg(%s);", p->name)
_( "}")
_( "")
_( "static void freevars(void) {")
- for (const struct ent *p = ents; p - ents < nents; ++p) {
+ for (const struct conent *p = conents; p - conents < nconents; ++p) {
if (p->isvar) {
-F( " extfree(%s->strval);", p->name);
+F( " extfree(%s->strval);", p->name)
}
}
_( "}")
if (fclose(out) == EOF) die("couldn't fully write cmdinit.gen.h");
+ out = fopen(".build/include/featureinit.gen.h", "wb");
+ if (!out) die("couldn't open featureinit.gen.h");
+ H()
+ // XXX: I dunno whether this should just be defined in sst.c. It's sort of
+ // internal to the generated stuff hence tucking it away here, but that's at
+ // the cost of extra string-spaghettiness
+_( "enum {")
+_( " FEAT_OK,")
+_( " FEAT_REQFAIL,")
+_( " FEAT_PREFAIL,")
+_( " FEAT_NOGD,")
+_( " FEAT_NOGLOBAL,")
+_( " FEAT_FAIL")
+_( "};")
+_( "")
+_( "static const char *const featmsgs[] = {")
+_( " \" [ OK! ] %s\\n\",")
+_( " \" [ skipped ] %s (requires another feature)\\n\",")
+_( " \" [ skipped ] %s (not applicable or useful)\\n\",")
+_( " \" [ unsupported ] %s (missing gamedata)\\n\",")
+_( " \" [ FAILED! ] %s (failed to access engine)\\n\",")
+_( " \" [ FAILED! ] %s (error in initialisation)\\n\"")
+_( "};")
+_( "")
+ for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) {
+ if (f->has_preinit) {
+F( "extern bool _feature_preinit_%s(void);", f->modname)
+ }
+F( "extern bool _feature_init_%s(void);", f->modname)
+ if (f->has_end) {
+F( "extern bool _feature_end_%s(void);", f->modname)
+ }
+ if (f->is_requested) {
+F( "bool has_%s = false;", f->modname)
+ }
+ else if (f->has_end || f->has_evhandlers) {
+F( "static bool has_%s = false;", f->modname)
+ }
+ }
+_( "")
+_( "static void initfeatures(void) {")
+ for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) {
+ featdfs(out, f);
+ }
+_( "")
+ // note: old success message is moved in here, to get the ordering right
+_( " con_colourmsg(RGBA(64, 255, 64, 255),")
+_( " LONGNAME \" v\" VERSION \" successfully loaded\");")
+_( " con_colourmsg(RGBA(255, 255, 255, 255), \" for game \");")
+_( " con_colourmsg(RGBA(0, 255, 255, 255), \"%s\\n\", gameinfo_title);")
+_( " struct con_colour white = {255, 255, 255, 255};")
+_( " struct con_colour green = {128, 255, 128, 255};")
+_( " struct con_colour red = {255, 128, 128, 255};")
+_( " con_colourmsg(&white, \"---- List of plugin features ---\\n\");");
+ for (const struct feature *f = features_bydesc.x[0]; f;
+ f = f->hdr_bydesc.x[0]) {
+F( " con_colourmsg(status_%s == FEAT_OK ? &green : &red,", f->modname)
+F( " featmsgs[(int)status_%s], \"%s\");", f->modname, f->desc)
+ }
+_( "}")
+_( "")
+_( "static void endfeatures(void) {")
+ for (struct feature **pp = endstack.data + endstack.sz - 1;
+ pp - endstack.data >= 0; --pp) {
+ if ((*pp)->has_end) {
+F( " if (has_%s) _feature_end_%s();", (*pp)->modname, (*pp)->modname)
+ }
+ }
+_( "}")
+_( "")
+ if (fclose(out) == EOF) die("couldn't fully write featureinit.gen.h");
+
out = fopen(".build/include/evglue.gen.h", "wb");
if (!out) die("couldn't open evglue.gen.h");
- H()
+ H_()
for (const struct event *e = events.x[0]; e; e = e->hdr.x[0]) {
+_( "")
F( "void _evemit_%s(void) {", e->name)
- for (const char **pp = e->handlers.data;
+ for (usize *pp = e->handlers.data;
pp - e->handlers.data < e->handlers.sz; ++pp) {
-F( " void _evhandler_%s_%s(void);", *pp, e->name); // blegh.
-F( " _evhandler_%s_%s();", *pp, e->name);
+ const char *modname = (const char *)(*pp & ~1ull);
+F( " void _evhandler_%s_%s(void);", modname, e->name) // blegh.
+ if (*pp & 1ull) {
+ // note: has_* variables are already included by this point (above)
+F( " if (has_%s) _evhandler_%s_%s();", modname, modname, e->name)
+ }
+ else {
+F( " _evhandler_%s_%s();", modname, e->name)
+ }
}
_( "}")
}
diff --git a/src/democustom.c b/src/democustom.c
index 0a1174f..c7de77b 100644
--- a/src/democustom.c
+++ b/src/democustom.c
@@ -22,12 +22,18 @@
#include "demorec.h"
#include "engineapi.h"
#include "errmsg.h"
+#include "feature.h"
#include "gamedata.h"
#include "intdefs.h"
#include "mem.h"
#include "ppmagic.h"
#include "vcall.h"
+FEATURE()
+REQUIRE(demorec)
+REQUIRE_GAMEDATA(vtidx_GetEngineBuildNumber)
+REQUIRE_GAMEDATA(vtidx_RecordPacket)
+
static int nbits_msgtype, nbits_datalen;
// The engine allows usermessages up to 255 bytes, we add 2 bytes of overhead,
@@ -107,12 +113,7 @@ static bool find_WriteMessages(void) {
DECL_VFUNC_DYN(int, GetEngineBuildNumber)
-bool democustom_init(void) {
- if (!has_vtidx_GetEngineBuildNumber || !has_vtidx_RecordPacket) {
- errmsg_errorx("missing gamedata entries for this engine");
- return false;
- }
-
+INIT {
// More UncraftedkNowledge:
// > yeah okay so [the usermessage length is] 11 bits if the demo protocol
// > is 11 or if the game is l4d2 and the network protocol is 2042.
diff --git a/src/democustom.h b/src/democustom.h
index 9f9bbe9..a0a28b8 100644
--- a/src/democustom.h
+++ b/src/democustom.h
@@ -17,8 +17,6 @@
#ifndef INC_DEMOCUSTOM_H
#define INC_DEMOCUSTOM_H
-#include <stdbool.h>
-
/* maximum length of a custom demo message, in bytes */
#define DEMOCUSTOM_MSG_MAX 253
@@ -28,8 +26,6 @@
*/
void democustom_write(const void *buf, int len);
-bool democustom_init(void);
-
#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/demorec.c b/src/demorec.c
index 1fb5e8e..50128f0 100644
--- a/src/demorec.c
+++ b/src/demorec.c
@@ -21,6 +21,7 @@
#include "con_.h"
#include "engineapi.h"
#include "errmsg.h"
+#include "feature.h"
#include "gamedata.h"
#include "gameinfo.h"
#include "hook.h"
@@ -32,6 +33,9 @@
#include "x86.h"
#include "x86util.h"
+FEATURE("improved demo recording")
+REQUIRE_GAMEDATA(vtidx_StopRecording)
+
DEF_CVAR(sst_autorecord, "Continuously record demos even after reconnecting", 1,
CON_ARCHIVE | CON_HIDDEN)
@@ -187,7 +191,7 @@ static inline bool find_recmembers(void *stoprecording) {
recording = mem_offset(demorecorder, mem_load32(p + 2));
}
if (recording && demonum) return true; // blegh
- NEXT_INSN(p, "state variables");
+ NEXT_INSN(p, "recording state variables");
}
#else // linux is probably different here idk
#warning TODO(linux): implement linux equivalent (???)
@@ -195,11 +199,7 @@ static inline bool find_recmembers(void *stoprecording) {
return false;
}
-bool demorec_init(void) {
- if (!has_vtidx_StopRecording) {
- errmsg_errorx("missing gamedata entries for this engine");
- return false;
- }
+INIT {
cmd_record = con_findcmd("record");
if (!cmd_record) { // can *this* even happen? I hope not!
errmsg_errorx("couldn't find \"record\" command");
@@ -240,7 +240,7 @@ bool demorec_init(void) {
return true;
}
-void demorec_end(void) {
+END {
// avoid dumb edge case if someone somehow records and immediately unloads
if (*recording && *demonum == 0) *demonum = 1;
void **vtable = *(void ***)demorecorder;
diff --git a/src/demorec.h b/src/demorec.h
index 7306c0a..40f8c38 100644
--- a/src/demorec.h
+++ b/src/demorec.h
@@ -18,14 +18,9 @@
#ifndef INC_DEMOREC_H
#define INC_DEMOREC_H
-#include <stdbool.h>
-
/* For internal use by democustom */
extern void *demorecorder;
-bool demorec_init(void);
-void demorec_end(void);
-
#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/engineapi.c b/src/engineapi.c
index 15c780a..cef085b 100644
--- a/src/engineapi.c
+++ b/src/engineapi.c
@@ -14,8 +14,8 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdbool.h> // used in generated code
-#include <stdlib.h> // "
+#include <stdbool.h>
+#include <stdlib.h> // used in generated code
#include <string.h> // "
#include "con_.h"
diff --git a/src/ent.c b/src/ent.c
index 392fceb..9d69bf0 100644
--- a/src/ent.c
+++ b/src/ent.c
@@ -18,12 +18,15 @@
#include "engineapi.h"
#include "errmsg.h"
+#include "feature.h"
#include "gamedata.h"
#include "gametype.h"
#include "intdefs.h"
#include "mem.h"
#include "vcall.h"
+FEATURE()
+
DECL_VFUNC_DYN(void *, PEntityOfEntIndex, int)
static struct edict **edicts = 0;
@@ -44,7 +47,7 @@ void *ent_get(int idx) {
return e->ent_unknown;
}
-bool ent_init(void) {
+INIT {
// for PEntityOfEntIndex we don't really have to do any more init, we
// can just call the function later.
if (has_vtidx_PEntityOfEntIndex) return true;
diff --git a/src/ent.h b/src/ent.h
index 98657ce..049f27f 100644
--- a/src/ent.h
+++ b/src/ent.h
@@ -17,16 +17,11 @@
#ifndef INC_ENT_H
#define INC_ENT_H
-#include <stdbool.h>
-
#include "engineapi.h"
struct edict *ent_getedict(int idx);
void *ent_get(int idx);
-bool ent_init(void);
-void ent_end(void);
-
#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/event.h b/src/event.h
index 41965e9..e439201 100644
--- a/src/event.h
+++ b/src/event.h
@@ -24,7 +24,7 @@
#define DEF_EVENT(evname) \
DECL_EVENT(evname) \
static inline void _evown_##evname(void) { _evemit_##evname(); }
-#define EMIT_EVENT(evname) _evown_##evname();
+#define EMIT_EVENT(evname) _evown_##evname()
#define HANDLE_EVENT(evname) \
void _EVENT_CAT4(_evhandler_, MODULE_NAME, _, evname)(void) \
diff --git a/src/feature.h b/src/feature.h
new file mode 100644
index 0000000..5277bca
--- /dev/null
+++ b/src/feature.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef INC_FEATURE_H
+#define INC_FEATURE_H
+
+#include <stdbool.h>
+
+#define _FEATURE_CAT1(a, b) a##b
+#define _FEATURE_CAT(a, b) _FEATURE_CAT1(a, b)
+
+/*
+ * Declares that this translation unit implements a "feature" - a unit of
+ * plugin functionality.
+ *
+ * desc specifies a string to be displayed to the user. Omit this to declare an
+ * internal feature, which won't be advertised, but will be available to other
+ * features.
+ */
+#define FEATURE(... /*desc*/)
+
+/*
+ * Indicates that the specified feature is required for this feature to function.
+ * If that feature fails to initialise, this feature will not be enabled.
+ */
+#define REQUIRE(feature)
+
+/*
+ * Indicates that the specified feature should be initialised before this one,
+ * but is not a hard requirement.
+ *
+ * Presence of a feature can be tested for using has_<featurename>.
+ */
+#define REQUEST(featname) extern bool has_##featname;
+
+/*
+ * Indicates that the specified gamedata entry is required for this feature to
+ * function. If that entry is missing, this feature will not be enabled.
+ *
+ * Note that optional gamedata doesn't need to be specified here as it has no
+ * effect on whether this feature is loaded. It can simply be tested for using
+ * has_<entryname>.
+ */
+#define REQUIRE_GAMEDATA(feature)
+
+/*
+ * Indicates that this feature requires a global variable (such as a factory or
+ * globally-exposed engine interface) to be non-null in order to function. If
+ * the variable has a null/zero value prior to feature initialisation, this
+ * feature will not be enabled.
+ */
+#define REQUIRE_GLOBAL(varname)
+
+/*
+ * Defines the special feature init function which is unique to this translation
+ * unit. This should return true to indicate success, or false to indicate
+ * failure. Features which start to load will cause dependent features not to be
+ * started.
+ *
+ * Features are required to specify this function.
+ */
+#define INIT bool _FEATURE_CAT(_feature_init_, MODULE_NAME)(void) // { code... }
+
+/*
+ * Defines the special, optional feature shutdown function which is unique to
+ * this translation unit. This does not return a value, and may be either
+ * specified once, or left out if no cleanup is required for this feature.
+ */
+#define END void _FEATURE_CAT(_feature_end_, MODULE_NAME)(void) // { code... }
+
+/*
+ * Defines a conditional check to run prior to checking other requirements for
+ * this feature. This can be used to match a certain game type or conditionally
+ * register console variables, and should return true or false to indicate
+ * whether the feature should continue to initialise.
+ */
+#define PREINIT bool _FEATURE_CAT(_feature_preinit_, MODULE_NAME)(void) // {...}
+
+#endif
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/fov.c b/src/fov.c
index 4ea9b2d..c7c72ed 100644
--- a/src/fov.c
+++ b/src/fov.c
@@ -24,6 +24,7 @@
#include "errmsg.h"
#include "ent.h"
#include "event.h"
+#include "feature.h"
#include "gametype.h"
#include "hook.h"
#include "intdefs.h"
@@ -31,8 +32,11 @@
#include "sst.h"
#include "vcall.h"
#include "x86.h"
+#include "x86util.h"
+
+FEATURE("extended FOV range")
+REQUEST(ent)
-bool changedmax = false;
DEF_CVAR_MINMAX_UNREG(fov_desired,
"Set the base field of view (SST reimplementation)", 75, 75, 120,
CON_HIDDEN | CON_ARCHIVE)
@@ -58,13 +62,7 @@ static bool find_SetDefaultFOV(struct con_cmd *fov) {
mem_loadoffset(p + 1));
return true;
}
- int len = x86_len(p);
- if (len == -1) {
- errmsg_errorx("unknown or invalid instruction looking for %s",
- "SetDefaultFOV");
- return false;
- }
- p += len;
+ NEXT_INSN(p, "SetDefaultFOV");
}
return false;
}
@@ -75,7 +73,7 @@ static void fovcb(struct con_var *v) {
if (player) orig_SetDefaultFOV(player, con_getvari(v));
}
-// called by sst.c in ClientActive to ensure fov is applied on load
+// ensure FOV is applied on load, if the engine wouldn't do that itself
HANDLE_EVENT(ClientActive) {
if (real_fov_desired == fov_desired) {
void *player = ent_get(1); // "
@@ -85,14 +83,15 @@ HANDLE_EVENT(ClientActive) {
static struct con_cmd *cmd_fov;
-bool fov_init(bool has_ent) {
+PREINIT {
// could work for other games, but generally only portal 1 people want this
// (the rest of us consider this cheating and a problem for runs...)
- if (!GAMETYPE_MATCHES(Portal1)) { return false; }
+ return GAMETYPE_MATCHES(Portal1);
+}
+INIT {
cmd_fov = con_findcmd("fov");
if (!cmd_fov) return false; // shouldn't really happen but just in case!
-
if (real_fov_desired = con_findvar("fov_desired")) {
// latest steampipe already goes up to 120 fov
if (real_fov_desired->parent->maxval == 120) return false;
@@ -122,7 +121,7 @@ bool fov_init(bool has_ent) {
return true;
}
-void fov_end(void) {
+END {
if (real_fov_desired && real_fov_desired != fov_desired) {
real_fov_desired->parent->maxval = 90;
if (con_getvarf(real_fov_desired) > 90) {
diff --git a/src/hook.c b/src/hook.c
index 8465fbe..eacac4a 100644
--- a/src/hook.c
+++ b/src/hook.c
@@ -57,7 +57,7 @@ void *hook_inline(void *func_, void *target) {
// FIXME: these cases may result in somewhat dodgy error messaging. They
// shouldn't happen anyway though. Maybe if we're confident we just
// compile 'em out of release builds some day, but that sounds a little
- // scary. For now prefering confusing messages over crashes, I guess.
+ // scary. For now preferring confusing messages over crashes, I guess.
if (func[len] == X86_CALL) {
con_warn("hook_inline: can't trampoline call instructions\n");
return 0;
diff --git a/src/l4dwarp.c b/src/l4dwarp.c
index 76aa230..5412b64 100644
--- a/src/l4dwarp.c
+++ b/src/l4dwarp.c
@@ -16,18 +16,23 @@
#define _USE_MATH_DEFINES // ... windows.
#include <math.h>
-#include <stdbool.h>
#include "con_.h"
#include "engineapi.h"
#include "errmsg.h"
#include "ent.h"
+#include "feature.h"
#include "gamedata.h"
#include "gametype.h"
#include "intdefs.h"
#include "mem.h"
#include "vcall.h"
+FEATURE("Left 4 Dead warp testing")
+REQUIRE_GAMEDATA(off_entpos)
+REQUIRE_GAMEDATA(off_eyeang)
+REQUIRE_GAMEDATA(vtidx_Teleport)
+
DECL_VFUNC_DYN(void *, GetBaseEntity)
DECL_VFUNC_DYN(void, Teleport, const struct vec3f *pos, const struct vec3f *ang,
const struct vec3f *vel)
@@ -48,12 +53,11 @@ DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you",
org->y + shift * sin(yaw), org->z}, 0, &(struct vec3f){0, 0, 0});
}
-bool l4dwarp_init(void) {
- if (!GAMETYPE_MATCHES(L4Dx)) return false;
- if (!has_off_entpos || !has_off_eyeang || !has_vtidx_Teleport) {
- errmsg_errorx("missing gamedata entries for this engine");
- return false;
- }
+PREINIT {
+ return GAMETYPE_MATCHES(L4Dx);
+}
+
+INIT {
con_reg(sst_l4d_testwarp);
return true;
}
diff --git a/src/l4dwarp.h b/src/l4dwarp.h
deleted file mode 100644
index 5328fcf..0000000
--- a/src/l4dwarp.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef INC_L4DWARP_H
-#define INC_L4DWARP_H
-
-#include <stdbool.h>
-
-bool l4dwarp_init(void);
-
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/nosleep.c b/src/nosleep.c
index 5618ba9..07a5500 100644
--- a/src/nosleep.c
+++ b/src/nosleep.c
@@ -14,16 +14,19 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdbool.h>
-
#include "con_.h"
#include "engineapi.h"
#include "errmsg.h"
+#include "feature.h"
#include "gamedata.h"
#include "hook.h"
#include "os.h"
#include "vcall.h"
+FEATURE("inactive window sleep adjustment")
+REQUIRE_GAMEDATA(vtidx_SleepUntilInput)
+REQUIRE_GLOBAL(factory_inputsystem)
+
DEF_CVAR_UNREG(engine_no_focus_sleep,
"Delay while tabbed out (SST reimplementation)", 50,
CON_ARCHIVE | CON_HIDDEN)
@@ -36,19 +39,13 @@ static void VCALLCONV hook_SleepUntilInput(void *this, int timeout) {
orig_SleepUntilInput(this, con_getvari(engine_no_focus_sleep));
}
-bool nosleep_init(void) {
- struct con_var *v = con_findvar("engine_no_focus_sleep");
- if (v) return false; // no need!
+PREINIT {
+ if (con_findvar("engine_no_focus_sleep")) return false;
con_reg(engine_no_focus_sleep);
- // TODO(featgen): auto-check these factories
- if (!factory_inputsystem) {
- errmsg_errorx("missing required factories");
- return false;
- }
- if (!has_vtidx_SleepUntilInput) {
- errmsg_errorx("missing gamedata entries for this engine");
- return false;
- }
+ return true;
+}
+
+INIT {
void *insys = factory_inputsystem("InputSystemVersion001", 0);
if (!insys) {
errmsg_errorx("couldn't get input system interface");
@@ -66,7 +63,7 @@ bool nosleep_init(void) {
return true;
}
-void nosleep_end(void) {
+END {
unhook_vtable(vtable, vtidx_SleepUntilInput, (void *)orig_SleepUntilInput);
}
diff --git a/src/nosleep.h b/src/nosleep.h
deleted file mode 100644
index 4d5237f..0000000
--- a/src/nosleep.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef INC_NOSLEEP_H
-#define INC_NOSLEEP_H
-
-#include <stdbool.h>
-
-bool nosleep_init(void);
-void nosleep_end(void);
-
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/portalcolours.c b/src/portalcolours.c
index 750ee19..231aa01 100644
--- a/src/portalcolours.c
+++ b/src/portalcolours.c
@@ -21,13 +21,18 @@
#include "engineapi.h"
#include "errmsg.h"
#include "gametype.h"
+#include "feature.h"
#include "hook.h"
#include "intdefs.h"
#include "mem.h"
#include "os.h"
#include "ppmagic.h"
+#include "sst.h"
#include "vcall.h"
+FEATURE("portal gun colour customisation")
+REQUIRE_GLOBAL(clientlib)
+
// It's like the thing Portal Tools does, but at runtime!
DEF_CVAR(sst_portal_colour0, "Crosshair colour for gravity beam (hex)",
@@ -119,8 +124,11 @@ static bool find_UTIL_Portal_Color(void *base) {
return false;
}
-bool portalcolours_init(void *clientlib) { // ... should libs be globals?
- if (!GAMETYPE_MATCHES(Portal)) return false;
+PREINIT {
+ return GAMETYPE_MATCHES(Portal1);
+}
+
+INIT {
#ifdef _WIN32
if (!find_UTIL_Portal_Color(clientlib)) {
errmsg_errorx("couldn't find UTIL_Portal_Color");
@@ -145,7 +153,7 @@ bool portalcolours_init(void *clientlib) { // ... should libs be globals?
#endif
}
-void portalcolours_end(void) {
+END {
unhook_inline((void *)orig_UTIL_Portal_Color);
}
diff --git a/src/portalcolours.h b/src/portalcolours.h
deleted file mode 100644
index 2dd4bb5..0000000
--- a/src/portalcolours.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef INC_PORTALCOLOURS_H
-#define INC_PORTALCOLOURS_H
-
-#include <stdbool.h>
-
-bool portalcolours_init(void *clientlib);
-void portalcolours_end();
-
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/rinput.c b/src/rinput.c
index 1b81cf5..a7ad8d8 100644
--- a/src/rinput.c
+++ b/src/rinput.c
@@ -17,14 +17,16 @@
// NOTE: compiled on Windows only. All Linux Source releases are new enough to
// have raw input already.
-#include <stdbool.h>
#include <Windows.h>
#include "con_.h"
#include "hook.h"
#include "errmsg.h"
+#include "feature.h"
#include "intdefs.h"
+FEATURE("raw mouse input")
+
// We reimplement m_rawinput by hooking cursor functions in the same way as
// RInput (it's way easier than replacing all the mouse-handling internals of
// the actual engine). We also take the same window class it does in order to
@@ -76,11 +78,14 @@ static int __stdcall hook_SetCursorPos(int x, int y) {
return orig_SetCursorPos(x, y);
}
-bool rinput_init(void) {
+PREINIT {
if (con_findvar("m_rawinput")) return false; // no need!
// create cvar hidden so if we fail to init, setting can still be preserved
con_reg(m_rawinput);
+ return true;
+}
+INIT {
WNDCLASSEXW wc = {
.cbSize = sizeof(wc),
// cast because inproc is binary-compatible but doesn't use stupid
@@ -139,7 +144,7 @@ e0: UnregisterClassW(L"RInput", 0);
return false;
}
-void rinput_end(void) {
+END {
RAWINPUTDEVICE rd = {
.dwFlags = RIDEV_REMOVE,
.hwndTarget = 0,
diff --git a/src/rinput.h b/src/rinput.h
deleted file mode 100644
index e89a363..0000000
--- a/src/rinput.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef INC_RINPUT_H
-#define INC_RINPUT_H
-#ifdef _WIN32
-
-#include <stdbool.h>
-
-bool rinput_init(void);
-void rinput_end(void);
-
-#endif
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/sst.c b/src/sst.c
index d2576f6..c959d65 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -22,26 +22,15 @@
#endif
#include "ac.h"
-#include "bind.h"
-#include "alias.h"
-#include "autojump.h"
#include "con_.h"
-#include "democustom.h"
-#include "demorec.h"
#include "engineapi.h"
#include "errmsg.h"
-#include "ent.h"
#include "event.h"
-#include "fov.h"
#include "fixes.h"
#include "gameinfo.h"
#include "gametype.h"
#include "hook.h"
-#include "l4dwarp.h"
-#include "nosleep.h"
-#include "portalcolours.h"
#include "os.h"
-#include "rinput.h"
#include "vcall.h"
#include "version.h"
@@ -53,8 +42,9 @@
static int ifacever;
-// we need to keep this reference to dlclose() it later - see below
-static void *clientlib = 0;
+// XXX: exposing this clumsily to portalcolours. we should have a better way of
+// exposing lib handles in general, probably.
+void *clientlib = 0;
#ifdef _WIN32
extern long __ImageBase; // this is actually the PE header struct but don't care
@@ -189,16 +179,6 @@ static void VCALLCONV SetCommandClient(void *this, int i) { con_cmdclient = i; }
static const void **vtable_firstdiff;
static const void *const *const plugin_obj;
-// TODO(featgen): I wanted some nice fancy automatic feature system that
-// figures out the dependencies at build time and generates all the init glue
-// but we want to actually release the plugin this decade so for now I'm just
-// plonking some bools here and worrying about it later. :^)
-static bool has_ac = false, has_autojump = false, has_demorec = false,
- has_fov = false, has_nosleep = false, has_portalcolours = false;
-#ifdef _WIN32
-static bool has_rinput = false;
-#endif
-
static bool already_loaded = false, skip_unload = false;
#define RGBA(r, g, b, a) (&(struct con_colour){(r), (g), (b), (a)})
@@ -208,28 +188,12 @@ static const char *updatenotes = "\
* various internal cleanup\n\
";
+#include <featureinit.gen.h>
+
static void do_featureinit(void) {
- bool has_bind = bind_init();
- if (has_bind) has_ac = ac_init();
- alias_init();
- has_autojump = autojump_init();
- has_demorec = demorec_init();
- if (has_demorec) democustom_init();
- bool has_ent = ent_init();
- has_fov = fov_init(has_ent);
- if (has_ent) l4dwarp_init();
- has_nosleep = nosleep_init();
- if (clientlib) has_portalcolours = portalcolours_init(clientlib);
-#ifdef _WIN32
- has_rinput = rinput_init();
-#endif
+ initfeatures();
fixes_apply();
- con_colourmsg(RGBA(64, 255, 64, 255),
- LONGNAME " v" VERSION " successfully loaded");
- con_colourmsg(RGBA(255, 255, 255, 255), " for game ");
- con_colourmsg(RGBA(0, 255, 255, 255), "%s\n", gameinfo_title);
-
// if we're autoloaded and the external autoupdate script downloaded a new
// version, let the user know about the cool new stuff!
if (getenv("SST_UPDATED")) {
@@ -416,16 +380,7 @@ static void do_unload(void) {
}
#endif
- if (has_ac) ac_end();
- if (has_autojump) autojump_end();
- if (has_demorec) demorec_end();
- if (has_fov) fov_end(); // dep on ent
- if (has_nosleep) nosleep_end();
- if (has_portalcolours) portalcolours_end();
-#ifdef _WIN32
- if (has_rinput) rinput_end();
-#endif
-
+ endfeatures();
#ifdef __linux__
if (clientlib) dlclose(clientlib);
#endif
@@ -475,12 +430,19 @@ DEF_CVAR(_sst_onload_echo, "EXPERIMENTAL! Don't rely on this existing!", "",
CON_HIDDEN)
DEF_EVENT(ClientActive)
+DEF_EVENT(Tick)
+
+// Quick and easy server tick event. Eventually, we might want a deeper hook
+// for anything timing-sensitive, but this will do for our current needs.
+static void VCALLCONV GameFrame(void *this, bool simulating) {
+ EMIT_EVENT(Tick);
+}
static void VCALLCONV ClientActive(void *this, struct edict *player) {
// XXX: it's kind of dumb that we get handed the edict here then go look it
// up again in fov.c but I can't be bothered refactoring any further now
// that this finally works, do something later lol
- EMIT_EVENT(ClientActive)
+ EMIT_EVENT(ClientActive);
// continuing dumb portal hack. didn't even seem worth adding a feature for
if (has_vtidx_ServerCommand && con_getvarstr(_sst_onload_echo)[0]) {
@@ -510,7 +472,7 @@ static const void *vtable[MAX_VTABLE_FUNCS] = {
(void *)&GetPluginDescription,
(void *)&nop_p_v, // LevelInit
(void *)&nop_pii_v, // ServerActivate
- (void *)&nop_b_v, // GameFrame
+ (void *)&GameFrame, // GameFrame
(void *)&nop_v_v, // LevelShutdown
(void *)&ClientActive
// At this point, Alien Swarm and Portal 2 add ClientFullyConnect, so we
diff --git a/src/sst.h b/src/sst.h
index 247346b..d432480 100644
--- a/src/sst.h
+++ b/src/sst.h
@@ -19,7 +19,12 @@
#include "event.h"
+/* misc stuff that doesn't belong anywhere else */
+
DECL_EVENT(ClientActive)
+DECL_EVENT(Tick)
+
+extern void *clientlib;
#endif