From e38d94416f705aa4ddd32e1c51199b45b58d6093 Mon Sep 17 00:00:00 2001 From: Danny Holman Date: Fri, 25 Oct 2024 01:49:29 -0500 Subject: doc: add introductory documentation files Add documentation files that act as the starting points for the rest of the documentation and introduce a beginner to Rune. Signed-off-by: Danny Holman --- doc/coding-style.rst | 284 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 doc/coding-style.rst (limited to 'doc/coding-style.rst') diff --git a/doc/coding-style.rst b/doc/coding-style.rst new file mode 100644 index 0000000..68c2240 --- /dev/null +++ b/doc/coding-style.rst @@ -0,0 +1,284 @@ +Rune Engine coding style +======================== + +This is a short document describing the preferred coding style for the Rune +Engine. This coding style is largely inspired by the Linux kernel coding style, +with some tweaks to make it friendly to modern editors. + +Indentation +----------- + +Tabs are 8 spaces, tab characters should not be used. This makes the code look +the same across editors and systems, and forces developers to simplify their +code wherever possible. Large indentations make each code block easier to +identify at a glance. + +.. code-block:: c + + int main(int argc, char* argv[]) { + if (rune_init(argc, argv) != 0) { + log_output(LOG_FATAL, "Error!"); + return -1; + } + + int running = 1; + while (running) { + // main loop + } + + rune_exit(); + } + +Branch statements should not be placed on the same line; it makes it look like +you have something to hide: + +.. code-block:: c + + if (condition) do_this; + do_something_always; + +Braces should be used on multiple statements: + +.. code-block:: c + + if (condition) { + do_this(); + do_that(); + } + +Assignments should be placed on their own lines. The coding style is very simple +and fancy expressions are frowned upon. + +Long lines +---------- + +The preferred limit on the length of a single line is 80 columns, however, some +exceptions are made based on readability. Otherwise, lines longer than 80 +columns should ideally be broken down into sensible chunks. A commonly used +technique is to align descendants to a function open parenthesis. However, +user-visible strings should *never* be broken as that breaks the ability to grep +for them. + +Braces and spaces +----------------- + +The opening brace should *always* be placed last on the same line and the +closing brace first on the following line. This applies to all functions and +statement blocks. If a statement is followed by a continuation, it is placed +right after the brace. This allows easy insertion and deletion of interposing +statements: + +.. code-block:: c + + int func(void) { + } + + switch (value) { + case 0: + break; + default: + return; + } + + if (x == 1) { + here(); + then_here(); + over_there_as_well(); + } else { + do_this(); + } + +An exception is when a single statement under a branch: no braces are required +unless one of the branches has multiple lines. In addition, braces should be +used on any loop that has more than a single statement. + +Spaces should be placed on each side of most binary and ternary operators, such +as the following: + +.. code-block:: c + + = + - < > * / % | & ^ <= >= == != ? : + +However, spaces should *not* be placed after unary operators, increment and +decrement operators and structure member access operators. + +Unnecessary whitespace at the end of lines or files is frowned upon. Some +editors with smart indentation will leave whitespace at the beginning of new +lines so you can start typing the next line right away. However, many editors +do not erase the whitespace should you choose not to continue typing. + +Git will warn about trailing whitespace, and can optionally strip it out for +you; however, if applying a series of patches, this may make later patches in +the series fail by changing context lines. + +Naming things +------------- + +Encoding the type into the function or variable name can become misleading at +best, or dead wrong at worst. The compiler already knows what type the object +in question is, and relaying this information to the programmer is unnecessary. +Furthermore, overly descriptive names for variables are frowned upon, unless the +description boosts readability. + +Local variables should be short and to the point. Loop counter should just be +called ``i``. Giving temporary and single-use variables long, descriptive names +is counterproductive and makes the code that much harder to read. + +Global variables should be avoided, but in cases where they *must* be used, they +should have a name that reflects their purpose and scope. For instance: + +.. code-block:: c + + int index_counter = 0; + vec3 clear_color = {0.0f, 0.0f, 0.0f, 1.0f}; + +Typedefs +-------- + +Typedefs should be avoided at the global or API level, as it can hide +information about a struct or pointer. Things such as ``type_t`` are prone to +create confusion and are heavily frowned upon. The rationale is that when a +programmer sees ``type_t var``, they have no context as to how the type needs +to be used. Instead, leave the backing type information in full view: + +.. code-block:: c + + struct type *var; + +The engine has already defined the standard C23 integer types for use in +engine code, so there is no need to do any kind of typedef. Even if the backing +type information is platform-dependant, a simple ``void*`` is almost always +preferable to creating a new type wholesale. + +Functions +--------- + +Functions should be short and (ideally) do one operation. They should fit on +no more than one or two 1080p screens, and any more than that is a sign the +function is too complex. Never be afraid to define a helper function local to +the function's translation unit. If a function is no more than five lines long, +it can be inlined for performance. Helper functions should be prefixed with an +underscore and marked ``static``, so that they aren't visible by the API. + +The number of local variables should not exceed five or six, and any more than +that is a good sign of an overly complex function. If you need more than that, +the function's design should be re-thought. Psychology says that the average +human brain can only hold between five and seven things in working memory, so +functions should be designed accordingly. In source files, functions should be +separated with a single blank line. In header files, any function made part of +the API should be marked with ``RAPI`` before the function signature. + +In function prototypes, the function name should be included with the type +information. This adds valuble information for the reader and makes function +arguments easier to track in debuggers. + +Commenting +---------- + +The engine uses doxygen-style comments for generating documentation. Outside of +this, comments should be used to explain the what and why of a piece of code. +Avoid putting comments inside a function body unless its a TODO or FIXME tag. +Generally, comments should be placed at the head of a function for documentation +purposes, especially engine API functions. + +.. code-block:: c + + /** + * \brief Example function + * \param[in] ex1 Example input + * \param[out] ex2 Example output + * \return Example return + */ + int example(int ex1, int *ex2); + +Structs should also be commented in a similar style: + +.. code-block:: c + + /** + * Example struct + */ + struct example { + int ID; ///< Identifier + struct member *m; ///< Example member + } + +Member doc comments should be tabbed over so that they are in the same column. + +Data structures +---------------- + +Data structures that have visibility outside the local thread should have a +reference count. The engine provides a reference accounting structure that is +thread-safe and works atomically. If you think your code will be visible outside +the current thread, don't hesitate to use it. It is better to be overly cautious +and use more memory than to take unnecessary risks with thread-global data. + +In addition, reference counting means the engine can avoid locking, and allows +multiple threads to access the data structure in parallel, or merely ensuring +the underlying data doesn't get taken away during a lock or other operation. + +A good rule of thumb is that if another thread can find your structure, and it +doesn't contain a reference count, it is almost always a bug. + +Macros and enums +---------------- + +All macros, enum values and constants should be capitalized. + +.. code-block:: c + + #define PI 3.1415926535 + + enum state { + STATE_READY, + STATE_UNREADY, + STATE_LOCKED + ... + } + + int GRAVITY = 9.8; + +Generally, inline functions are preferred to macros resembling functions, as +bugs in macros are much harder to track down and don't appear in a debugger. +Macros with multiple statements should be enclosed in a do-while block: + +.. code-block:: c + + #define MACRO(a, b, c) \ + do { \ + if (a == 5) \ + do_this(b, c); \ + } while(0) + +You should avoid macros that affect control flow, depend on having a local +variable with a magic name and macros that are used as l-values. Furthermore, +macros that have a more complex expression should be enclosed in parentheses. + +.. code-block:: c + + #define PI 3.14159265 + #define AREA(x) (PI * x * x) + +Macros should take into account common variable names, especially those +resembling functions. For instance: + +.. code-block:: c + + #define FOO(x) \ + ({ \ + typeof(x) ret; \ + }) + +Conditional compilation +----------------------- + +Wherever possible, the use of preprocessor conditionals should be relegated to +header files. If you need to define a different function (say, for a different +platform) it is best to define functions for each case and a stub for the final +else. + +References +---------- + +* `Linux kernel coding style `_ -- cgit v1.2.3