diff options
Diffstat (limited to 'DevDocs')
| -rw-r--r-- | DevDocs/code-style.txt | 850 | 
1 files changed, 850 insertions, 0 deletions
diff --git a/DevDocs/code-style.txt b/DevDocs/code-style.txt new file mode 100644 index 0000000..e909887 --- /dev/null +++ b/DevDocs/code-style.txt @@ -0,0 +1,850 @@ +════ mike’s code style guide ════ + +I have a lot of implicit hand-wavy rules about code structure and style which I +never really formalised anywhere. Mostly this is because I don’t believe in +having a totally rigid set of formatting rules and instead try to make the form +of each chunk of code the best compromise for that particular bit of code. + +A lot of that is based on vibes and subjective judgement. There are essentially +no hard rules. + +This document aims to state some of my thoughts and feelings on such matters so +that anyone looking to contribute patches can at least aim to approximate my +style a little closer. It might also give some insight into why all my code is +so awesome and/or terrible. + +Please don’t get too caught up in this though. It’s very unlikely that you’ll +consistently make the exact aesthetic judgements I would make because we don’t +share the same brain. For all intents and purposes, you should be quite happy +not to have my brain, so don’t worry about it. + +Also, this guide does not pertain to the actual programming logic side of +things. For that, you should refer to code-standards.txt , once that is written. +The plan is to get round to that at some point eventually. + +══ First, the hard rules ══ + +Okay, I lied, there actually are some hard rules, but mostly about the actual +data on disk rather than anything stylistic: + +• Source files are text files. This means every line ends in with an end-of-line +  character (\n), including the last line. If your editor mistreats the final \n +  as an extra blank line, that’s a you problem. Lines without end-of-lines are +  not lines and text files ending in non-lines are not text files. I will die on +  this hill. + +• \r\n is not a line ending. It is an erroneous carriage return character, +  followed by a line ending. Your source files generally should not include +  weird non-printable characters, least of all at the end of every single line. +  Exception: Windows batch files, which require a carriage return at the end of +  each line to ensure that labels/gotos work correctly. + +• Git for Windows is configured to wildly misbehave by default. If you have not +  set core.eol=lf and core.autocrlf=false prior to cloning this repo, and Git +  starts mangling file contents in confusing ways, then that’s a you problem. + +• SST uses UTF-8-encoded source files. It should go without saying but there +  should be no BOM. Generally comments will use ASCII only for ease of typing, +  although we use Unicode in the copyright headers to be fancy, because we can, +  and if you gain something by putting Unicode characters in a doc comment, then +  hell, go for it. + +• String literals with Unicode in them should put the UTF-8 straight in the +  literal rather than doing the obfuscated \U nonsense. Although, be aware that +  the environment SST runs in is prone to completely bungling Unicode display, +  so most user-facing strings inside the codebase are gonna be limited to ASCII. + +• No spaces or Unicode in file paths. That’s just obnoxious. + +• We use tabs for indentation, and line width guidelines (see below) assume a +  tab width of 4 spaces, but you can configure this to 8 or something for +  reading purposes if you prefer. This configurability is one reason we don’t +  indent with spaces. Another reason is that text editors still behave more +  stupid with spaces. I don’t care what anyone says about how a good editor will +  make spaces feel exactly the same as tabs. Empirical evidence shows that that +  is clearly bollocks. + +• The asterisk goes on the right in pointer declarations. This is dictated by +  the syntax and semantics of the language. Putting it on the left is +  misleading, and looks wrong to most C programmers — as it should, because it +  is. People coming from C++ may have to unlearn a bad habit here, but on the +  plus side, when they go back to C++, they can do it correctly there now too! + +• No spaces between function names and parentheses. Yes spaces between control +  flow keywords and parentheses. No space padding inside parentheses. Also no +  space padding inside braces for initialisers, although yes spaces for small +  struct declarations. Don’t ask me why on that last one, it’s just how I do it. + +• Spaces around all arithmetic operators. No spaces around dots or arrows; +  hopefully this one’s not contentious‽ + +• Spaces between string literal pieces and/or macros representing literal +  pieces. This one’s actually a dumb C++ one that can probably go but it’s +  everywhere and I don’t really care so just stick to it for consistency. + +• No spaces between casts and castees. + +• Just look at the code. Do what the code does. The rest of the rules will cover +  nebulous edge cases but these really basic things should be easy. I trust you! + +══ Nebulous formatting and whitespace guidelines ══ + +In no particular order: + +• Usually each statement should go on its own line for obvious reasons, but very +  simple statements that are very closely related can go together if it improves +  the information density of the code without sacrificing clarity. + +  Rationale: +  I like to maximise the amount of information being conveyed in the smallest +  amount of space, especially on the vertical axis. Being able to fit more +  context on screen at once makes it easier to keep track of what’s going on in +  complex regions of code. + +  Bad example: +    ++x; +    ++y; +    ++z; + +  Good example: +    ++x; ++y; ++z; + +• Try and keep lines under 80 characters. If wrapping would look really stupid +  and unbalanced and the last character on the line is not important to the +  understanding of the line, 81 might do. In very rare cases, we can suffer 82. + +  Rationale: +  People who argue about We Don’t Use 80 Character Terminals Any More™ are +  missing the point. Really long lines are just obnoxious to read. Plus, it’s +  nice to be able to fit 3 or 4 files on screen side-by-side (or views of the +  same file if it’s a big one!). However, sometimes you just need to squeeze in +  a semicolon and realistically 80 is an arbitrary limit, so it’s okay to apply +  something like the TeX badness system where you trade one form of ugliness for +  a lesser form. In this case, an overly long line to avoid really ugly wrapping. + +• Continue hard-wrapped lines with 2 tabs of indentation. + +  Rationale: +  I don’t have a good reason for this. I used to do it in Java when I was a +  child and now my editor still does it by default in C so it’s just kind of +  what I do. I think it looks fine and at least one other option is worse. + +  "Bad" example: +    if (cond1 && +      cond2) { +        do_thing(); +    } + +  "Good" example: +    if (cond1 && +            cond2) { +        do_thing(); +    } + +• Try to hard-wrap lines at a natural reading break point. Don’t just wrap on a +  word if you can keep an entire string literal or struct initialiser together +  for instance. This is ultimately a subjective judgement, but do your best. + +  Rationale: +  Keeping individual _things_ like strings or structs together makes it a little +  easier to scan with the eyes. It also makes it a lot easier to grep. If you +  break a string into lines mid-phrase, grep is ruined. If you really need to +  break up a long string literal, at least putting the break between sentences +  will make grepping less likely to fail. + +  Bad example: +    errmsg_warn("could not do operation with rather lengthy description: failed" +            " to lookup that important thing"); + +  Good example: +    errmsg_warn("could not do operation with rather lengthy description: " +            "failed to lookup that important thing"); + +• Hard wrap a little earlier if it avoids leaving a single dangling token which, +  as with poorly-typeset prose, just looks stupid. + +  Rationale: +  You want something substantial on each line for your eyes to lock onto. Dangly +  diminutive tailpieces look stupid and ugly and have no friends. + +  Bad example: +    call_function("parameter 1 takes up some space", param_2 + 5, another_var, +          x); + +  Good example: +    call_function("parameter 1 takes up some space", param_2 + 5, +            another_var, x); + +  Maybe better example (it’s up to you): +    call_function("parameter 1 takes up some space", +            param_2 + 5, another_var, x); + +  Note: that maybe better example would become a definitely better example if +  the middle two (or last three) parameters were related in some way. + +• Don’t hard wrap before punctuation. Closing parentheses, commas, semicolons, +  conditional operators and so on should remain attached to a preceding token. + +  Rationale: +  Starting a line with punctuation in code looks just as wonky to me as it would +  in natural language prose. Ending on punctuation also primes my brain to read +  a continuation on the next line. + +  Bad example: +    if (cond1 +            && cond2) + +  Good example: +    if (cond1 && +            cond2) + +• An open brace should always be on the same line as the function header or +  conditional statement that precedes it. Even if a line is wrapped, do not +  leave the brace on its own. Take something else with it. + +  Rationale: +  I prefer the visual flow of a brace being attached to a thing and think it +  looks kind of silly on its own line. I also strongly avoid wasting vertical +  space in most cases. + +  Bad example: +    void my_function_name() +    { +      // ... +    } + +  Good example: +    void my_function_name() { +      // ... +    } + +• Control flow statements should always use braces if the body is on its own +  line, even if it’s only one statement. No classic Unix/BSD-style hanging +  statements. + +  Rationale: +  It’s harder to introduce stupid bugs if the addition of a new statement to a +  block does not require fiddling with braces, and it’s less editing work to add +  or remove statements when the braces are always there. Braces also provide a +  stronger visual association with the controlling statement than just an +  indent. + +  Bad example: +    if (err) +        invoke_error_handler(err, context, "tried to do thing and it failed!"); + +  Good example: +    if (err) { +        invoke_error_handler(err, context, "tried to do thing and it failed!"); +    } + +• If a control flow statement and its associated single statement can fit on one +  line, then go ahead and do that, with no need for braces. If there’s more than +  one statement but they’re very trivial and/or closely related, a braced +  one-liner is also alright. + +  Rationale: +  This saves a ton of vertical space for trivial conditions, especially when +  there’s a bunch of them in a row. + +  Bad example: +    if (err) { +        return false; +    } +    if (extrastuff) { +        process_florbs(); +        refresh_grumbles(); +    } + +  Good example: +    if (err) return false; +    if (extrastuff) { process_florbs(); refresh_grumbles(); } + +• Paired if/else statements should either both be one-liners (or maybe even a +  one-liner altogether), or both use braces. No mixing and matching. + +  Rationale: +  This improves the visual rhythm and consistency when reading through the file. +  It reduces the extent to which a reader’s eyes must jump around the screen to +  find the correct bit of text to read. + +  Bad example: +    if (!p) errmsg_warn("unexpected null value"); +    else { +        p->nsprungles += 71; +        defrombulate(p); +        errmsg_note("we do be defrombulating them sprungles!!!"); +    } + +  Good example: +    if (!p) { +        errmsg_warn("unexpected null value"); +    } +    else { +        p->nsprungles += 71; +        defrombulate(p); +        errmsg_note("we do be defrombulating them sprungles!!!"); +    } + +• When putting the else on its own line, put it on its own line. Don’t hug the +  closing brace of the if. + +  Rationale: +  Having the if and else line up in the same column looks better to me when +  scanning vertically through the file. It also makes it easier to edit and move +  things aruond when selection can be done linewise, and easier to comment stuff +  out temporarily for debugging and such. + +  Bad example: +    if (cond) { +        do_some_cool_stuff(); +    } else { +        do_something_else(); +    } + +  Good example: +    if (cond) { +        do_some_cool_stuff(); +    } +    else { +        do_something_else(); +    } + +• If there are a bunch of related conditionals in a row, and only some of them +  fit on one line, consider keeping them all braced to improve the visual +  symmetry. + +  Rationale: +  This reads better, similarly to the if-else example, and is easier to edit. + +  Bad example: +    if (cond1) return; +    if (cond2 || flag3 == THIS_IS_VERY_LONG_BY_COMPARISON) { +        do_other_thing(false); +    } +    if (cond3 || flag3 == SHORTER_ONE) continue; + +  Good example (note that this one is way more subjective and case-by-case!): +    if (cond1) { +        return; +    } +    if (cond2 || flag3 == THIS_IS_VERY_LONG_BY_COMPARISON) { +        do_other_thing(false); +    } +    if (cond3 || flag3 == SHORTER_ONE) { +        continue; +    } + +• Favour inter-line visual symmetry over compactness of any individual line. + +  Rationale: +  Basically a generalised form of many of these other rules. It seemed like a +  good idea to just state this outright. Plus, it lets me give a very specific +  example that would be hard to shoehorn in elsewhere. + +  Bad example: +  x[0] = y[0]; x[1] = y[1]; x[2] = y[2]; x[3] = y[3]; x[4] = y[4]; +  x[5] = y[5]; x[6] = y[6]; x[7] = y[7]; + +  Good example: +  x[0] = y[0]; x[1] = y[1]; x[2] = y[2]; x[3] = y[3]; +  x[4] = y[4]; x[5] = y[5]; x[6] = y[6]; x[7] = y[7]; + +• Spread large struct initialisers over multiple lines. Don’t do the usual +  two-tab hard wrapping, but instead do a brace indent thing and put each member +  on a line. + +  Rationale: +  Visual weight and symmetry, and ease of amendment. As usual. + +  Bad example: +    struct gizmo g = {"this string exists to make the example line long!", 15, +            sizeof(whatever)}; + +  Good example: +    struct gizmo g = { +        "this string exists to make the example line long!", +        15, +        sizeof(whatever) +    }; + +• As an exception to the above, very closely related things might go on the same +  line. Use judgement as to whether that grouping makes things clearer or worse. + +  Rationale: +  Hopefully by now the theme is emerging that I value hard rule consistency less +  than moment-to-moment clarity. In cases where most things are on different +  lines, grouping things on the same line can sometimes aid clarity and/or +  information density. It’s the same idea as grouping statements/declarations. + +  Examples: N/A. This is way too subjective and situational to make up a +  contrived example. But you’ll see it sometimes in my code and you’re welcome +  to do it yourself whenever the vibes are right. It’s all just vibes. + +• Declare multiple *related* variables of the same type on one line as a +  comma-separated list. + +  Rationale: +  This makes things more terse and compact in cases when the grouping is +  obvious. As soon as the declarations need to span multiple lines, there seems +  to be less benefit to doing this, but in cases where there’s a small number of +  things and the compaction creates no real loss of clarity, it makes sense. + +  Bad example: +    int a; +    int b; +    int c; +    char *x; +    char *y; + +  Good example (assuming these things are related): +    int a, b, c; +    char *x, *y; + +• Cram goto labels into the indent margins, on the same line as the actual code. + +  Rationale: +  Labels can introduce visual noise, especially in the case of many cascading/ +  unwinding error cleanup handlers. Considering that goto use tends to be +  limited, it doesn’t seem that important to give highly descriptive names to +  labels. + +  Bad example: +    void myfunc() { +        if (!thing1()) goto err_1; +        if (!thing2()) goto err_2; +        return true; + +    err_2: +        cleanup1(); +    err_1: +        cleanup2(); +        return false; +    } + +  Good example: +    void myfunc() { +        if (!thing1()) goto e1; +        if (!thing2()) goto e2; +        return true; + +    e2: cleanup1(); +    e1: cleanup2(); +        return false; +    } + +• Don’t indent preprocessor directives. + +  Rationale: +  The preprocessor is its own meta language and it’s not really helpful to +  intermingle it with the main control flow syntax. It also just kind of looks +  subjectively messy to have preprocessor stuff reaching far across the screen. +  I do see the argument for indenting nested ifdefs, but usually there’s few +  enough of them that it won’t be that confusing either way and in cases where +  there’s a lot of nesting… well, you have bigger problems. + +  Bad example: +    #ifndef _WIN32 +        #ifdef __LP64__ +            #define IS64BIT 1 +        #else +            #define IS64BIT 0 +        #endif +    #endif + +  Good example: +    #ifndef _WIN32 +    #ifdef __LP64__ +    #define IS64BIT 1 +    #else +    #define IS64BIT 0 +    #endif +    #endif + +• Use C++/C99-style comments for explanatory notes, and classic C-style comments +  for documentation in headers etc. As an exception, C-style comments can be +  used inside multi-line macro bodies with line continuation, but should not be +  padded with extra asterisks in such cases. + +  Rationale: +  It’s nice to have a way to quickly visually distinguish public documentation +  from internal notes. To me, the single-line comments have a bit less formality +  about them, so they’re good for quickly jotting something down, whereas the +  big banner comments stand out a bit more like formal declarations of important +  information. + +  Bad example: +    // This function computes the sum of two integers. It assumes the result +    // will not overflow. +    int sum(int x, int y) { +        return x + y; /* no need for a comment here but this is an example! */ +    } + +    #define DO_INTERESTING_CALCULATION() do { \ +        /* +         * There's quite a lot to explain here. We not only have to increment +         * one of the numbers, but we also have to multiply the result by +         * another number, and then multiply that by yet another number, and add +         * 9 to that. This behaviour is specified in detail in one of the WHATWG +         * specs, presumably. +         */ \ +        ++foo; \ +        bar *= foo; \ +        baz = baz * bar + 9; \ +    } while (0) + +  Good example: +    /* +     * This function computes the sum of two integers. It assumes the result +     * will not overflow. +     */ +    int sum(int x, int y) { +        return x + y; // no need for a comment here but this is an example! +    } + +    #define DO_INTERESTING_CALCULATION() do { \ +        /* There's quite a lot to explain here. We not only have to increment +           one of the numbers, but we also have to multiply the result by +           another number. I won't repeat the entire stupid joke again. */ \ +        ++foo; \ +        bar *= foo; \ +        baz = baz * bar + 9; \ +    } while (0) + +    Caveat: note that generally the /**/ documentation comments should also go +    in the header rather than the implementation file, as this allows for both +    easy manual reading and LSP hover support, but whatever, you get the idea. + +• Put short comments pertaining to single lines of code at the ends of those +  lines if you can make them fit. Otherwise, write them above the relevant code. +  Do not continue an end-of-line comment onto an over-indented blank line. + +  Rationale: +  Probably obvious by now. Don’t waste space. Having comments hang off the ends +  of lines and melt down the right-hand side of the screen not only wastes space +  but also makes you look like a child writing with crayons. + +  Bad example: +    if (cond1 || cond2) reasonably_long_call(param1); // either conditiom can +                                                      // trigger this here due +                                                      // to that thing I +                                                      // mentioned. +  Good example: +    // either condition cam trigger this here due to that thing I mentioned. +    if (cond1 || cond2) reasonably_long_call(param1); + +  Pro tip: if you use Vim or Neovim as I do, consider doing set fo-=oc in your +  config to stop this dangling idiocy from happening when you take a new line. + +══ The second hard problem in computer science ══ + +… Naming things! + +Firstly we have some rules about interfacing with Valve’s code, since that’s +kind of a core thing we have to do in this project: + +• Use British English in all code and documentation. + +  Rationale: +  I am from Scotland. + +  Bad example: void analyze(); +  Good example: void analyse(); + +• Use exact SDK names (where known) for Source engine types (structs/classes). + +  Rationale: +  This makes it trivial to look things up in the official SDK sources. Since the +  naming here differs greatly from the naming elsewhere it stands out as being +  its own thing. There are some exceptions, notably the con_var and con_cmd +  structs which are our names for ConVar and ConCommand. The reasoning for those +  is that they are effectively part of SST’s internal API, so we’ve made them +  stylistically our own. + +  Bad example: +    struct baseent { +        void *vtable; +        // ... +    }; + +  Good example: +    struct CBaseEntity { +        void *vtable; +        // ... +    }; + +• Generally, prefer exact names for SDK functions too. + +  Rationale: +  Same as above; it makes it obvious which bit of the engine we’re referring to. + +  Bad example: +    DECL_VFUNC(struct CGameMovement, bool, checkjump) + +  Good example: +    DECL_VFUNC(struct CGameMovement, bool, CheckJumpButton) + +• Don’t bother with exact names for struct members or other variables. + +  Rationale: +  A lot of the time we’re poking into internals and in many cases the variables +  aren’t even at consistent byte offsets from one game version to the next (see +  also gamedata files). Many of Valve’s chosen variable names are also very +  annoying and violate every other guideline you’re about to see, so at the +  granularity of local variables I made the decision long ago to write whatever +  names made sense to me and not worry about it. + +  Bad example: +    int buttons = mv->m_iButtons; + +  Good example: +    int buttons = mv->buttons; + +The rest of these points apply to first-party code, i.e., SST itself: + +• Functions, variable names and struct/enum tags should all be lowercase. + +  Rationale: +    TitleCase is annoying to type and camelCase looks ugly. Most C code uses +    lowercase stuff so this looks the most familiar to most people, probably. + +  Bad example: +    int myVar; +    struct MyStruct; + +  Good example: +    int myvar; +    struct mystruct; + +• typedefs should almost never be used, except for base integer type +  abbrevations (intdefs.h) and function pointers. The latter are annoying to +  write out over and over so the typical convention (in cases like hooking) is +  to declare a FunctionName_func and then use that everywhere else. Even when +  dealing with e.g. Windows APIs which typedef the hell out of everything, try +  to avoid actually using those typedefs unless your hand is truly forced. + +  Rationale: +  C doesn’t treat typedefs as different types, so all typedefs really achieve on +  numeric and pointer types is obfuscation. In the case of structs, I always +  liked having the separate struct namespace, and using a typedef obviously goes +  against that. In more recent times I have *somewhat* thought about changing my +  mind on that, but it’d be a big change and I still don’t feel that strongly in +  favour of it. Also, since everything is lowercase, the struct keyword does +  help disambiguate a bit. If we were gonna typedef structs, there’d probably +  also be some discussion about using TitleCase or something. + +• const definitions and enum values should be uppercase. + +  Rationale: +  This distinguishes them from variables, indicating that they are essentially +  build-time symbols that get substituted by the compiler, similar to macros +  (see also the next point). + +  Bad example: static const int maxents = 64; +  Good example: static const int MAXENTS = 64; + +  Caveat: +  I feel like I might have unthinkingly broken this rule myself at some point, +  and at the time of writing I’m not intent on doing a full cleanup pass to find +  out. Said cleanup pass is bound happen at some point though, don’t worry. + +• The case of macros… kind of depends. If your macro is a constant, or a +  function-like macro that expands to a blob of context-dependent syntax, then +  it should be uppercase to indicate that it’s effectively also a symbolic +  constant. If your macro essentially behaves as an inline function, then it +  should be lowercase, although you should also consider just writing an inline +  function instead. If your macro aims to extend the syntax of C like the things +  in langext.h, it can also be lowercase in order to look like a real keyword. + +  Rationale: I’m very accustomed to using uppercase for constants but not all +  macros behave as constants. My usual goal with macros is for the interface not +  to feel like macros, even if the implementation is macros. That principle +  becomes really important when you see how horrendous some of the macros can +  get under the hood. + +  Bad example: +    #define MyConstant 4 +    #define FOREACH_THING(x) /* impl here */ +    #define doerrorcheck(var) do if (!var) die("couldn't get " #var); while (0) + +  Good example: +    #define MYCONSTANT 4 +    #define foreach_thing(x) /* impl here */ +    #define DOERRORCHECK(var) do if (!var) die("couldn't get " #var); while (0) + +• A few special things actually do use TitleCase, namely event names and +  gametype tags. + +  Rationale: +  These are sort of special things, and always passed into macros. They’re not +  regular tokens that you’d see anywhere else in the code, so it sort of felt +  right to give them a bit of a different style. Honestly, it’s all just vibes. + +  Bad example: +    DEF_EVENT(redraw_copilot_widget) +    X(portal4) /* (in GAMETYPE_BASETAGS in gametype.h) */ + +  Good example: +    DEF_EVENT(RedrawCopilotWidget) +    X(Portal4) /* (in GAMETYPE_BASETAGS in gametype.h) */ + +• Prefer the shortest name you can get away with. If you feel that a longer name +  is necessary for understanding, that is okay. But if you can abbreviate +  without losing too much clarity, then that is a good idea. If your variable is +  local to a small scope you can probably get away with an even shorter name. + +  Rationale: +  The only research I’ve seen on the topic indicates that longer names do not +  meaningfully quicken debugging speed even for developers unfamiliar with a +  codebase. Since I prefer higher information density, I strongly prefer +  cramming more structure into the same amount of space via smaller individual +  tokens, rather than having longer and more redundant names. You’ll see a ton +  of abbreviations in this codebase and some of them might seem a bit obtuse to +  begin with, but it seems that other people have been able to get used to that. + +  Bad example: +    bool success = check_conditions(has_required_data, parse_integer(input)); + +  Good example: +    bool ok = checkcond(has_data, parseint(input)); + +• Try to make a name slightly shorter again if you can avoid wrapping. + +  Rationale: all the previous whitespace rules aiming to minimise ugliness can +  often be aided by fudging the actual text a bit. See also: Tom 7’s “Badness 0” +  from SIGBOVIK 2024. https://youtu.be/Y65FRxE7uMc + +  Examples: can’t be bothered contriving one of these, but watch that video, +  it’s very funny. + +• Underscores are used for namespaces and semantic groupings. Don’t use them as +  spaces. Having two or three words joined without spaces is perfectly fine. + +  Rationale: +  Overusing underscores leads to long verbose names, which goes against the +  previous guideline. Calling a variable “gametypeid” instead of “game_type_id” +  is not, in my opinion, that much less readable, and it allows underscores to +  be reserved for a more specific purpose. This is not a hard rule in the event +  that you absolutely insist that an underscore looks significantly better, but +  be prepared for me to ignore your insistence some percentage of the time. + +  Examples: +  Exactly the same as above, really. Note the lack of underscores in the good +  example; has_data is an exception because we have a has_ convention throughout +  the codebase; it is essentially a namespace. + +• External-linkage (aka public) functions, variables, and structures should be +  prefixed with the module name, which is the source file basename. For +  instance, any part of the API defined in ent.c should generally be named +  ent_something. The same idea goes for uppercase names of macros and enum +  values, usually. + +  Rationale: +  This makes it extremely easy to tell where something is defined at a glance. +  It also encourages thinking about where stuff should go, since the best name +  for something will kind of dictate where it belongs *and* vice-versa. + +  Examples: N/A. This should be blindingly obvious, come on. + +• Special macros that define or declare things will begin with DEF_ or DECL_, +  respectively, rather than following the above rule. Again, there are no hard +  rules. Event handlers use HANDLE_EVENT and the feature.h macros just do +  whatever. + +  Rationale: +  In these specific cases, clarity was thought to be aided by having the purpose +  of the macro front and center, and in the case of DEF/DECL, by grouping them +  semantically with other similar macros across the codebase. Most macros will +  not be subject to such exceptions, but like everything in this codebase, you +  must always allow for exceptions, except in cases where you shouldn’t. + +  Examples: N/A. + +• Stuff that’s forced to go in public headers but isn’t supposed to be part of +  the interface should be prefixed with an underscore. + +  Rationale: this communicates that it’s unstable private API, and also stops it +  from showing up unhelpfully in autocompletion results. + +  Bad example: int mything_lookupinternalthingmy(); +  Good example: int _mything_lookupinternalthingmy(); + +══ General naming conventions ══ + +We have a few recurring patterns in the source, some of which are even required +for certain macros to work correctly! These include: + +• vtidx_* for virtual table indices, used by DECL_VFUNC_DYN. +• off_* for offsets to member variables, used by DEF_ACCESSORS. +• sz_* for sizes of structs/classes, used by DEF_ARRAYIDX_ACCESSOR. +• has_* in front of any generated gamedata/entprops value, to indicate the +  presence or absence of a known value at runtime, in the current game/engine. +  Also used in front of a feature name when REQUEST() is specified. + +Some other common prefixes and suffixes are just good for making the whole thing +feel coherent to a human reader even if nothing really requires them: + +• *_func for function pointer typedefs. +• orig_* for a function pointer used to call an original function from a hook. +• hook_* for the hook function itself, which intercepts calls to the original. +• find_* for a function within a feature which attempts to find some variable or +  function buried within the engine, usually by chasing pointers through machine +  instructions or internal engine data structures or something. + +And there’s a bunch of abbreviations I tend to use often without thinking, which +I’ve been told in the past may be confusing to new players: + +• ctxt for context +• sz for size +• cb for callback +• msg for message +• cmd for command +• insns for instructions +• ret or sometimes r for any return value +• i for a loop counter or temporary variable +• p or maybe ptr for a temporary pointer +• the first letter of any type name for a temporary variable in a narrow scope + +Hopefully you get the idea: there’s a general tendency toward terseness. Usually +the names don’t actually matter that much, to be honest. Most of the guidelines +around naming here have to do with upholding an overall structure and consistent +feeling when reading the codebase rather than worrying about the giving every +individual thing the perfect name. If one thing gets a really dumb name, the sky +will still be up there. You can always rename it later. The thing I mean, not +the sky. And also a sky by any name would smell just as sweet I mean a rose. + +══ Miscellany ══ + +• Trivial but worth mentioning: don’t write void for zero-argument functions. + +  Rationale: This is a C23 codebase. () is adequate. +  Bad example: void stub(void); +  Good example: void stub(); + +• Do still use {0} for default struct initialisation, rather than {}. + +  Rationale: C23 is still not that widely supported and I don’t want to demand a +  super-cutting-edge compiler version, in case something breaks. Eventually, +  after the dust has settled, this rule will be flipped over. + +  Bad example: struct gizmo g = {} +  Good example: struct gizmo g = {0}; + +• Always put the & in front of a function when creating a function pointer. + +  Rationale: it’s a pointer, innit. No need to rely on legacy implicit weirdness. +  Bad example: regcb(cb); +  Good example: regcb(&cb); + +That’s about it, I think. + +Thanks, and have fun! + +──────────────────────────────────────────────────────────────────────────────── +Copyright © Michael Smith <mikesmiffy128@gmail.com> + +Permission to use, copy, modify, and/or distribute this documentation file for +any purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THIS DOCUMENTATION IS PROVIDED “AS-IS” AND HAS NO WARRANTY.  | 
