Cemu-2024-03-05/CODING_STYLE.md

4.1 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 of m_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 wxString::FromUTF8(const std::string& s)
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.