1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
|
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 generally be avoided at the local level, however, at the global
or API level, a bare structure could become a bit too wordy for general use.
Instead, use a typedef to avoid hitting the 80-column limit unless it is
readable as it is.
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 for standard types.
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 <https://www.kernel.org/doc/html/v4.10/process/coding-style.html>`_
|