forked from splash-damage/coding-standards
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSplashDamageCodingStandard.cpp
More file actions
388 lines (338 loc) · 13.9 KB
/
SplashDamageCodingStandard.cpp
File metadata and controls
388 lines (338 loc) · 13.9 KB
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// BSD 3-Clause License
//
// Copyright (c) 2019, Splash Damage
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// @SPLASH_DAMAGE_FILE: <description>
// [markup.engine.file] If this file is being added to an engine location, mark this
// with the above comment at the start of the file. This removes the need for the
// SPLASH_DAMAGE_CHANGE engine guards around modifications in the file (see [markup.engine])
// [basic.layout] try to limit horizontal space and use vertical layout
// there is a reason newspapers have columns ;)
// 80 characters is a good guideline to strive for
// --------------------------------------------- 80 char limit --------------->|
// --------------------------------------------- 100 char limit ---------------------------------->|
// you can add an indicator with visual assist:
// Visual Assist Options -> Display -> Display indicator after column n
// [comment.type]
// always use the C++ style and not the C /**/ style
// because it shows up in searches
// Visual Studio shortcut to toggle comments on a block:
// Edit -> Advanced -> Un/Comment Selection
// Ctrl+K Ctrl+C / Ctrl+K Ctrl+U
// [header.iwuu] always include the equivalent .h file first
#include "SplashDamageCodingStandard.h"
#include "Components/PrimitiveComponent.h"
#include "UnrealNetwork.h"
// [cpp.namespace.private] use a namespace to wrap translation-unit local free functions
// defined only in cpp files. Also mark them as static to enforce internal only linkage.
namespace SDCodingStandardHelpers
{
static void PrivateHelper(const USDCodingStandardExampleComponent& Object)
{
}
}
// [basic.order] respect the order of declarations in the .h header
// when you write the definitions here
// [basic.rule.brace] FOLLOW UE4 coding style i.e. Allman style aka one every line
void BraceStyle()
{
// illustration purpose only - don't do this in live code (use bit sets instead of many bool's)
bool FailCondition = false, TrueCondition = true,
SomethingElse = true, Contract = true, Binding = true;
if (FailCondition)
{
return; // even for one liners
}
if (TrueCondition)
{
/* ... */
}
else if (SomethingElse)
{
/* ... */
}
else // !SomethingElse and !TrueCondition
{
/* ... */
}
// for `switch` statements follow Unreal's guideline
// https://docs.unrealengine.com/latest/INT/Programming/Development/CodingStandard/#switchstatements
// [basic.rule.parens] parenthesis link with the expression not the keyword
for (;/*...*/;)
{
}
// [cpp.return.early] use early returns to avoid excessive nesting
// especially for pre-conditions / contracts
// one exception is logic flow where too many early returns would hurt readability
if (!Contract && !Binding)
{
return;
}
// versus
if (Contract)
{
if (Binding)
{
}
}
// [cpp.if.init] use the if-with-initializer idiom
if (bool IsGame = FApp::IsGame())
{
/* ... */
}
}
// [globals.no] avoid globals unless they're POD (plain-old-data)
// they don't play well with Hot Reload and their order of initialization
// is undefined. Epic loves them but we don't!
FIntPoint CachedCoordinates; // PASSABLE
class MyBigObject
{
/* ... */
};
MyBigObject Cache1; // BAD
MyBigObject Cache2; // BAD - maybe this is started first, not Cache1
// [cpp.return] consider using TOptional for returns that can fail
// instead of using C style pass by reference
TOptional<FIntRect> IntersectTest(const FIntPoint &min, const FIntPoint &max)
{
/* ... */
return (min.X > max.X || min.Y > max.Y) ? TOptional<FIntRect>() : FIntRect(min, max);
}
// [ue.gen.struct] if Blueprint variables are extracted in separate structures
// it is possible to pass them around, thus not having to expose all functions as
// methods in a class, thus leading to less coupling and faster compilation
float DoPassBlueprintVarStructs(const FSDCodingStandardBlueprintVarGroup &vars)
{
return vars.CameraTraceVolumeWidth / 2.f;
}
void DontWasteMemory(AActor *Actor)
{
// [ue.container] Mind your allocations!
// don't go to the heap, go to the stack!
TInlineComponentArray<UPrimitiveComponent*> PrimComponents; // 24 item reserved by default
Actor->GetComponents(PrimComponents);
// [ue.container] [ue.ecs.get] Customize the get-ers for this purpose!
using TCustomAlloc = TInlineAllocator<32>;
TArray<UActorComponent *, TCustomAlloc> LocalItems;
Actor->GetComponents<UActorComponent, TCustomAlloc>(LocalItems);
// [ue.container.reserve] Prepare upfront the containers
// cut down on the need to allocate per-item
PrimComponents.Reserve(64);
PrimComponents.Init(nullptr, 64);
// [ue.container.reset] Don't empty, just reset!
PrimComponents.Empty(); // BAD - deallocates for new 0 size
PrimComponents.Reset(); // GOOD - same effect, but instant, no realloc
PrimComponents.Empty(64); // PASSABLE - empty with slack
// [hardware.cache] be mindful of cache access and plan your memory access accordingly
//
// 1 CPU cycle
// o
//
// L1 cache access
// ooo
//
// L2 cache access
// ooooooooo
//
// L3 cache access
// oooooooooooooooooooooooooooooooooooooooooo
//
// Main memory access
// ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
// ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
// ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
// ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
//
// from https://twitter.com/srigi/status/917998817051541504
//
// Some general tips
// - prefer linear data structures, don't jump via pointers too much etc
// - all cache access is through lines 64 bytes long, have that in mind!
// - in classes put related data together, group them by algorithm/logic access
// - be mindful of the invisible packing the compiler will do behind your back
// don't intermingle bool's or small types willy nilly with bigger ones etc
}
// [markup.engine] Use special markers for engine changes
void EngineChanges()
{
if (true)
{
// @SPLASH_DAMAGE_CHANGE: <author email> - BEGIN: <JIRA tag> <description>
/* ... */
// @SPLASH_DAMAGE_CHANGE: <author email> - END
}
// - always place the markers at column 1, no matter how indented the modified code is
// - if the END tag is too far from the BEGIN, repeat the description
// - if code is deleted rather than modified, discuss with lead the right approach
// i.e. commenting out the section vs actually removing it
}
void GameWithEditorChanges(TArray<int> Widgets)
{
// [markup.editor] isolate Editor specific changes in game code
#if WITH_EDITOR
/* ... */
// [assert.editor] never assert in Editor code - try to recover to your best effort!
check(Widgets.Num()); // <- BAD, will force-crash and potentially destroy work
ensureMsgf(Widgets.Num(), TEXT("Must have widgets selected!")); // <- GOOD, doesn't force-crash
#endif
}
// [cpp.auto] use `auto` or not at your discretion but BE CONSISTENT
// - if a part of code already has an auto style, follow it, don't mix
// - don't bikeshed over the merits of each style, pick one and stick with it
void AutoStyle()
{
// [cpp.auto.init] auto forces to have initialization - this is always good
auto Int = 42; // an int, if explicitly declared it would have garbage default value
auto MoreStuff = 42u; // this is unsigned int;
auto EvenMore = 42.f; // float
auto Precision = 42.0; // double
auto Condition = false; // boolean
auto Big = 42ll; // long long
int *PtrInt = ∬
int &RefInt = Int;
// auto on its own NEVER DEDUCES references or const'ness
auto not_what_you_think = static_cast<int &>(Int); // <- BAD: type is `int`
auto still_bad = static_cast<const int>(Int); // <- BAD: type is still `int`
// [cpp.auto.golden-rule] ALWAYS mark auto with the appropriate qualifiers:
// const, &, * - even if it's superfluous
auto &proper_ref = Int;
auto &enforce_ref = RefInt;
auto hidden_ptr = ∬ // <- BAD even if it still works
auto *explicit_ptr = PtrInt;
const auto &explicit_ref = RefInt;
// [cpp.auto.init.lbmd] a generalization of the always-initialized is the
// self calling lambda technique (bonus: very useful for `const`)
const auto InitLevel = []()
{
// possible example of complicated logic
// that cannot be easily implemented with the ?: operator
//
// if (auto CamMgr = (static_cast<ACameraManager *>(PC))->GetCameraManager())
// return CamMgr->GetCurrentHeightLevel();
// else
return 0;
}(); // <- called here immediately, so guaranteed to get a default value
// [cpp.auto.fwd] Don't use `auto &&` unless you know what you are doing
// i.e perfect forwarding inside a lambda
}
void NoAutoStyle()
{
// just don't use it and move on
//
// if you work in an area where it started with AutoStyle,
// continue it or refactor it all without auto
// mix of the 2 styles leads to poor readability and maintainability
}
void NumericLimits()
{
// [cpp.numericlimits] Use TNumericLimits instead of #defines such as FLT_MAX
// See http://api.unrealengine.com/INT/API/Runtime/Core/Math/TNumericLimits/
// E.g. For all floating point types
float MaxPositiveFloatValue = TNumericLimits<float>::Max();
float MinPositiveFloatValue = TNumericLimits<float>::Min();
float MinNegativeFloatValue = TNumericLimits<float>::Lowest();
// E.g. For integral types
int32 MaxPositiveIntValue = TNumericLimits<int32>::Max();
int32 MinNegativeIntValue = TNumericLimits<int32>::Min(); // This is the same as Lowest() for all integral types.
}
void ASDCodingStandardExampleActor::BeginPlay()
{
// [ue.ecs.super] always call Super:: method for Actor/Component tickable overridden functions
// other regular methods don't necessary need to do this
Super::BeginPlay();
}
void ASDCodingStandardExampleActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASDCodingStandardExampleActor, WantsToSprint);
}
void ASDCodingStandardExampleActor::OnRep_WantsToSprint()
{
}
void USDCodingStandardExampleComponent::LambdaStyle(AActor *ExternalEntity)
{
// [cpp.lambda.general] use lambda's to your advantage
// especially when they will isolate work in the implementation
// rather than pollute the interface with helper methods
//
// but don't abuse them - if their body becomes complex enough
// extract into separate function/method
// [cpp.lambda.dangling] biggest problem in production is creating dangling references
// by capturing objects by reference that die before the lambda gets called
auto lambda_dangling = [ExternalEntity]()
{
// will ExternalEntity still be valid at this point?
};
// [cpp.lambda.this] don't capture `this`!
// instead use the named captures to cherry pick
auto lambda_this = [LocalCopy = this->BlueprintGroup.ShowCameraWidget]()
{
// LocalCopy available irregardless of the fate of parent
};
// [cpp.lambda.=&] avoid capturing everything by value or worse, by reference!
auto lambda_avoid = [&]()
{
// HERE BE DRAGONS!
};
// [cpp.lambda.auto] the type deduction for the captures are quite convoluted
// - by reference: uses template rules; will preserve `const` and `&`
// - by value: uses template rules; will preserve `const`
// - init capture: uses `auto` rules; WILL MESS UP `const` and `&` (add them back manually)
const int Original = 0;
const int &Reference = Original;
auto lambda_auto = [Original, Duplicate = Original, &RefDuplicate = Original, NotReference = Reference]()
{
// Original => `const int`
// Duplicate => `int`
// RefDuplicate => `const int &`
// NotReference => `int`
};
}
static void EnumString()
{
// [cpp.enum.generated.count] Don't put a label to represent the number of values in the enum
// use EnumAutoGen::GetNumValues<ESDCodingStandardEnum>() instead.
constexpr auto NumValues = EnumAutoGen::GetNumValues<ESDCodingStandardEnum>();
// [cpp.enum.generated] Use the GenerateStringFuncs parameter if you need string conversion or
// query the number of values. Don't write boilerplate code for this yourself
constexpr auto * ValueAString = EnumAutoGen::GetEnumString(ESDCodingStandardEnum::ValueA);
// [cpp.enum.generated.foreach] Use ForEachPair, ForEachString, ForEachVal functions
// if you need to do something for each enum value or string
EnumAutoGen::ForEachPair<ESDCodingStandardEnum>(
[](ESDCodingStandardEnum Value, const TCHAR * String)
{
// Do something
});
}
void SDCodingStandardHelpers::PublicHelper(const USDCodingStandardExampleComponent& Object)
{
}
// [func.default.args] put in a C-style comment any default argument
void CreateSequence(int32 Start, int32 End, int32 Increment /* = 1 */)
{
}