-
Notifications
You must be signed in to change notification settings - Fork 75
Style guide
In general, completed documented files are a good place to look to understand project style in general.
Use the types from ultratypes.h
, not the standard C types: i.e. u8
,s8
,s16
,u16
,s32
,u32
,f32
rather than char
, short
, int
, float
and their signed
/unsigned
varieties.
We always write our enums and structs as typedef
s. (Usually one can't use an enum typedef as a function argument since enum typedefs are implicitly s32
, but it should be used when possible.)
Names are "big-endian": the most significant/largest part of the system goes first, e.g. DM_RAVINE_STATE_ACTIVE
rather than DM_RAVINE_ACTIVE_STATE
.
Type | Style | Example |
---|---|---|
Local variables | camelCase | yawToPlayer |
Global variables | gCamelCase | gSaveFile |
Static variables[^1] | sCamelCase | sZeroVec |
Struct members | camelCase | actionFunc |
Struct names | PascalCase | AnimationHeader |
Enum types | PascalCase | PlanetIds |
Enum values | SCREAMING_SNAKE_CASE | PLANET_CORNERIA |
Defines/macros | SCREAMING_SNAKE_CASE |
SCREEN_WIDTH ,ABS(x)
|
Functions | SystemName_FunctionName | Save_Write |
Files | snake_case | fox_beam.c |
[^1]: including in-function static
Action functions are usually named with a simple present-tense verb or verb phrase: {...}_Shoot
, {...}_Wait
, {...}_FallToGround
, etc. Setup functions are Setup{name of action}
.
Ideally names should be both short and clear, although it's better to be clear than short.
A lot of formatting is done by clang-format, such as
- indent is 4 spaces, tabs are not used
- case labels indented
- 120 column limit
- brackets go on the same line (
if (1) {
) - pointer goes on type (
s32* var;
nots32 *var;
)
There are various other conventions that it does not catch, though:
-
Blank line between declarations and code:
s32 var; func();
-
combine declarations and definitions if possible:
s32 var = 0; func();
instead of
s32 var; var = 0; func();
-
blank lines between switch cases if they're long (use your judgement).
- timers
- colours and alpha
- Usually array accesses and sizes
- angles
- Addresses
- Bitmasks (i.e.
& 0x80
etc.) - Struct offset comments
Numbers below 10
/0xA
do not need the 0x
if by themselves in code.
If a function returns only 0
or 1
, and is used as a boolean (i.e. in conditionals), replace the returns by false
and true
.
Floats usually need an f
on the end to match, or IDO will use doubles. Our floats are always of the form 1.0f
, even when the decimal part is zero.
-
Spacing out conditional or loop blocks from surrounding code often makes them easier to read.
-
Avoid assigning or mutating variables in conditionals if possible (including
++
/--
). -
We always use
{}
on conditional/loop blocks, even if they're one line (clang-tidy will enforce this). -
When conditions are
&&
d or||
d together, use brackets around each that includes an arithmetic comparison or bitwise operator (i.e. not!var
orfunc()
, but ones with==
or&
etc.) -
Flag checks or functions that return booleans do not need the
== 0
/!= 0
. -
Prefer
if-else
overif { return; }
, i.e.if (cond) { foo(); } else { bar(); }
over
if (cond) { foo(); return; } bar();
Become familiar with the various defines and enums we have available. There are too many to list all of them here, but the following are common:
- Those in
macros.h
-
ABS
,ABS_ALT
, -
CLAMP
and friends,
-
-
MTXMODE
for many of thesys_matrix
functions (yet to be created)
- It's better to not hardcode array sizes (easier to mod), however, this only applies to data that's already imported, for example:
f32 sSomeArray[] = {
2.0f, 2.4f, 8,7f
}
If the array is externed and the size is known, it's better to hardcode it to help in the data importing process:
extern f32 sSomeArray[3];
- Use
sizeof
orARRAY_COUNT
/ARRAY_COUNTU
where it makes sense, e.g. in loops that are using an array. - clang-format sometimes does weird things to array formatting. Experiment with and without a comma after the last element and see which looks better.
In general, pads should be s32
, or s16
/s8
if required.
Documentation includes:
- Naming functions
- Naming struct variables
- Naming data
- Naming local variables
- Describing the general purpose of the file
- Describing any unusual, interesting or strange features of how the file or parts of its content work
- Labelling and explaining bugs
- Making enums or defines for significant numbers for the file
- Naming the contents of the asset file(s) the file may use
If you are not sure what something does, it is better to leave it unnamed than name it wrongly. It is fine to make a note of something you are not sure about when PRing, it means the reviewers will pay special attention to it.
We use comments for:
- Top of file: a short description of the system.
/*
* File: fox_option.c
* Overlay: ovl_menu
* Description: Options Menu System
*/
- For function descriptions, we use multiline comments,
/**
* Describe what the function does
*/
These are optional: if you think the code is clear enough, you do not need to put a comment. You can use Doxygen formatting if you think it adds something, but it is also not required.
- If something in a function is strange, or unintuitive, do leave a comment explaining what's going on. We use
//
for this. - We also use
//
for temporary comments above a function. Feel free to useTODO:
in these if appropriate. - A bug should be commented with an
//! @bug Bug description
above the code that causes the bug.
All functions should go in the main C file in the same order as the assembly (the latter is required to match anyway). (We may make exceptions for particularly large files with a particular organisational structure, but we ask that you check on Discord first before doing this)
- If in doubt, leave all the data at the top of the file. Reviewers will decide for you.
- Data must go in the same order as in the assembly files, but is only constrained by other data, not functions or rodata.
- Some data has to be inline static to match. Generally it's better to not use
static
on data outside functions until the file is matching, sincestatic
data is left out of the mapfile and this makes debugging harder. - This is even more true of bss, where we have trouble with IDO unpredictably reordering it in certain files.
- For small arrays or simple data that is used in only one function, we usually inline it, if it fits in the ordering.