diff options
author | 2025-03-10 02:37:19 +0000 | |
---|---|---|
committer | 2025-04-06 16:41:13 +0100 | |
commit | 244fea664121acf12871ab5858a5fe95a2606b52 (patch) | |
tree | e42b1990ef97adc0f0ab48b9be7e11de7fee0558 /src/feature.h | |
parent | d86b7b41453c69b3854baa7cdc05a79a3cdfe092 (diff) | |
download | sst-244fea664121acf12871ab5858a5fe95a2606b52.tar.gz sst-244fea664121acf12871ab5858a5fe95a2606b52.zip |
Rewrite and redesign codegen and feature system
Also switch to somewhat proper C23 flags while we're at it.
This is a huge change. It took me forever, in between being really busy.
Sorry about that. But the good news is I'm now free to start integrating
the various patches that have accumulated since last release. Well, at
least in between still being really busy. Gotta manage expectations.
The main benefit of introducing GAMESPECIFIC() is that features
that don't apply to a particular game no longer show up *at all*, and
less time is wasted on init. It also enables a cool optimisation wherein
unnecessary REQUIRE_GAMEDATA() checks can elided at compile time
whenever the gamedata is known up-front to always exist in supported
games.
The DEF_FEAT_CVAR macro family meanwhile makes it easier to manage the
lifecycle of cvars/ccmds, with less manual registering, unhiding and
such.
Originally I was going to try and just hack these features into the
existing codegen abomination, but it just got too terrible. This rewrite
should make it easier to continue tweaking codegen behaviour in future.
It also has slightly better error messages.
Diffstat (limited to 'src/feature.h')
-rw-r--r-- | src/feature.h | 115 |
1 files changed, 92 insertions, 23 deletions
diff --git a/src/feature.h b/src/feature.h index e1e4688..81370b1 100644 --- a/src/feature.h +++ b/src/feature.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -21,18 +21,42 @@ #define _FEATURE_CAT(a, b) _FEATURE_CAT1(a, b) /* - * Declares that this translation unit implements a "feature" - a unit of - * plugin functionality. + * 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. + * At build time, the code generator automatically creates the glue code + * required to make features load and unload in the correct order, subject to + * compatibility with each supported game and engine version. As such, there is + * no need to manually plug a new feature into SST's initialisation code as long + * as dependency information is properly declared (see the macros below). + * + * desc specifies a string to be displayed to the user in the console printout + * that gets displayed when the plugin finishes loading. Omit this to declare an + * internal feature, which won't be displayed to users but will still be + * available for other features to call into and build functionality on top of. */ #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. + * Declares that this feature should only be loaded for games matching the given + * gametype tag (see gametype.h). Console variables and commands created using + * DEF_FEAT_* macros will not be registered if SST is loaded by some other game. + * + * As an optimisation, REQUIRE_GAMEDATA() checks (see below) can also be elided + * in cases where gamedata is always present for this particular game. As such, + * it is wise to still specify gamedata dependencies correctly, so that the + * definitions can be changed in the data files without breaking code. + */ +#define GAMESPECIFIC(tag) + +/* + * Indicates that the specified feature is required for this feature to + * function. If that feature fails to initialise, this feature will not be + * enabled. + * + * By convention, the name of the feature is the name of its implementation + * source file, minus the .c extension. For instance, foo.c would be referred to + * by REQUIRE(foo). */ #define REQUIRE(feature) @@ -40,9 +64,9 @@ * 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>. + * Presence of a feature can in turn be tested for using has_<featname>. */ -#define REQUEST(featname) extern bool has_##featname; +#define REQUEST(feature) extern bool has_##feature; /* * Indicates that the specified gamedata entry is required for this feature to @@ -52,40 +76,85 @@ * effect on whether this feature is loaded. It can simply be tested for using * has_<entryname>. */ -#define REQUIRE_GAMEDATA(feature) +#define REQUIRE_GAMEDATA(entry) /* * 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. + * + * Note that this really only works for variables known to engineapi.c which is + * kind of a bad abstraction, but it's currently just necessary in practice. + * + * Correct usage of this macro will generally be very similar to other usages + * found elsewhere in the codebase already, so use those as a reference. */ #define REQUIRE_GLOBAL(varname) +/* status values for INIT and PREINIT below */ +enum { + FEAT_SKIP = -1, /* feature isn't useful here, pretend it doesn't exist */ + FEAT_OK, /* feature successfully initialised/enabled */ + FEAT_FAIL, /* error in starting up feature */ + FEAT_INCOMPAT, /* feature is incompatible with this game/engine version */ + _FEAT_INTERNAL_STATUSES // internal detail, do not use +}; + /* - * 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. + * Defines the special feature initialisation function which is unique to this + * translation unit. All features are required to specify this function. + * + * The defined function must return FEAT_OK on success, FEAT_FAIL on failure due + * to some transient error, FEAT_INCOMPAT to indicate incompatibility with the + * current game/engine version, or FEAT_SKIP to indicate that the feature is + * useless or unnecessary. + * + * If a value other than FEAT_OK is returned, END (see below) will not be called + * later and other features that depend on this feature will be disabled. If + * this feature provides other functions as API, they can be assumed not to get + * called unless initialisation is successful. + * + * For features with a description (see FEATURE() above), all return values with + * the exception of FEAT_SKIP will cause a corresponding status message to be + * displayed in the listing after the plugin finishes loading, while FEAT_SKIP + * will simply hide the feature from the listing. Features with no description + * will not be displayed anyway. * - * Features are required to specify this function. + * Features which either fail to initialise or elect to skip loading will cause + * dependent features not to be enabled. */ -#define INIT bool _FEATURE_CAT(_feature_init_, MODULE_NAME)(void) // { code... } +#define INIT int _FEATURE_CAT(_feat_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... } +#define END void _FEATURE_CAT(_feat_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. + * Defines a special feature pre-init function which performs early feature + * initialisation, the moment the plugin is loaded. If the plugin is autoloaded + * via VDF, this will be called long before the deferred initialisation that + * usually happens after the client and VGUI have spun up. Since most of the + * rest of SST is also deferred, care must be taken not to call anything else + * that is not yet initialised. + * + * When in doubt, do not use this; it exists only to serve a couple of fringe + * cases. + * + * Like INIT above, the function created by this macro is expected to return one + * of FEAT_OK, FEAT_FAIL, FEAT_INCOMPAT, or FEAT_SKIP. If a value other than + * FEAT_OK is returned, the INIT block won't be run afterwards. + * + * Features that use this macro are currently disallowed from using REQUIRE() + * and REQUEST(), as well as GAMESPECIFIC(), because it's not clear how the + * semantics of doing so should work. It is still possible to use + * REQUIRE_GAMEDATA and REQUIRE_GLOBAL, however these only apply to the INIT + * block, *NOT* the PREINIT. */ -#define PREINIT bool _FEATURE_CAT(_feature_preinit_, MODULE_NAME)(void) // {...} +#define PREINIT int _FEATURE_CAT(_feat_preinit_, MODULE_NAME)(void) // {...} #endif |