4.0 KiB
Coding style guidelines for Cemu
This document describes the latest version of our coding-style guidelines. Since we did not use this style from the beginning, older code may not adhere to these guidelines. Nevertheless, use these rules even if the surrounding code does not match.
Cemu comes with a .clang-format
file which is supported by most IDEs for formatting. Avoid auto-reformatting whole files, PRs with a lot of formatting changes are difficult to review.
Names for variables, functions and classes
- Always prefix class member variables with
m_
- Always prefix static class variables with
s_
- For variable names: Camel case, starting with a lower case letter after the prefix. Examples:
m_option
,s_audioVolume
- For functions/class names: Use camel case starting with a capital letter. Examples:
MyClass
,SetActive
- Avoid underscores in variable names after the prefix. Use
m_myVariable
instead ofm_my_variable
About types
Cemu provides its own set of basic fixed-width types. They are:
uint8
, sint8
, uint16
, sint16
, uint32
, sint32
, uint64
, sint64
. Always use these types over something like uint32_t
. Using size_t
is also acceptable where suitable. Avoid C types like int
or long
. The only exception is when interacting with external libraries which expect these types as parameters.
When and where to put brackets
Always put curly-brackets ({ }
) on their own line. Example:
void FooBar()
{
if (m_hasFoo)
{
...
}
}
As an exception, you can put short lambdas onto the same line:
SomeFunc([]() { .... });
You can skip brackets for single-statement if
. Example:
if (cond)
action();
Printing
Avoid sprintf and similar C-style formatting API. Use fmt::format()
.
In UI related code you can use formatWxString
, but be aware that number formatting with this function will be locale dependent!
Strings and encoding
We use UTF-8 encoded std::string
where possible. Some conversions need special handling and we have helper functions for those:
// std::filesystem::path <-> std::string (in precompiled.h)
std::string _pathToUtf8(const fs::path& path);
fs::path _utf8ToPath(std::string_view input);
// wxString <-> std::string
wxString to_wxString(std::string_view str); // in gui/helpers.h
std::string wxString::utf8_string();
Logging
If you want to write to log.txt use cemuLog_log()
. The log type parameter should be mostly self-explanatory. Use LogType::Force
if you always want to log something. For example:
cemuLog_log(LogType::Force, "The value is {}", 123);
HLE and endianness
A pretty large part of Cemu's code base are re-implementations of various Cafe OS modules (e.g. coreinit.rpl
, gx2.rpl
...). These generally run in the context of the emulated process, thus special care has to be taken to use types with the correct size and endianness when interacting with memory.
Keep in mind that the emulated Espresso CPU is 32bit big-endian, while the host architectures targeted by Cemu are 64bit little-endian!
To keep code simple and remove the need for manual endian-swapping, Cemu has templates and aliases of the basic types with explicit endian-ness.
For big-endian types add the suffix be
. Example: uint32be
When you need to store a pointer in the guest's memory. Use MEMPTR<T>
. It will automatically store any pointer as 32bit big-endian. The pointer you store must point to memory that is within the guest address space.
HLE interfaces
The implementation for each HLE module is inside a namespace with a matching name. E.g. coreinit.rpl
functions go into coreinit
namespace.
To expose a new function as callable from within the emulated machine, use cafeExportRegister
or cafeExportRegisterFunc
. Here is a short example:
namespace coreinit
{
uint32 OSGetCoreCount()
{
return Espresso::CORE_COUNT;
}
void Init()
{
cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread);
}
}
You may also see some code which uses osLib_addFunction
directly. This is a deprecated way of registering functions.