- Generally follow Cpp11 standards & everything in the .clang-format
- Brackets must be attached to any statements that can have them (ex
ifs,fors, etc. No one-lining) - Always put a newline after a block (after a })
- Always use guards instead of nesting
- Prefix
g_for global variables (exg_program). - Prefix
m_for owned member variables in module classes. Note: this does not apply to game structs. - All
#definesshould be capitalized snake case. - Use
_tprimitive types whenever possible - Inline arrays when short enough
- If you are ever unsure of how to format just apply clang format
- All hooked functions with the hook manager should have the suffix
Hk - All trampolines should be formatted
static const auto trampoline = HookManager::Call(funcName); - All
TL_DECLARE_FUNCs should be named asClassName_functionNameorfunctionNameif not given a class - Do not use repeat hooked functions, instead move them to files where they are more shared
- Avoid C-style casting wherever possible
- Use EASTL types whenever they can replace std library functions (ex
eastl::vector&eastl::string) - Very rarely should you ever use the general std heap alloc
new, you should generally always use the game's memory arenas to be as safe as possible- Ensure the arena you are using actually makes sense for the case. You should not be allocating stuff into the server arena when in the client and vice versa.
- Examples:
Class* serverAllocatedClass = new (FB_SERVER_ARENA->alloc(sizeof(Class))) Class(args);reasonText = StringUtils::CopyWithArena("Timed out.", FB_CLIENT_ARENA);
- Use the
ThreadExecutorwhenever you are interacting with any game logic when not in the main game threads- Example:
g_threadExecutor->Queue(GameThread_Server, [this, cmd]() { g_program->m_console->EnqueueCommand(cmd) });
- Example:
- Use
Mutex<>on fields that may be accessed from multiple threads- Example usage:
MutexGuard<LoadLevelRequest> requestGuard = s_program->m_server->m_latestLoadLevelRequest.Lock();
- Example usage:
The launcher follows a feature-based architecture:
lib/
core/ # Shared application infrastructure
config/ # App-wide configuration (colors, strings, locales)
i18n/ # Internationalization
routing/ # App router configuration
services/ # Core services (storage, window, etc.)
utils/ # Utilities and extensions
features/ # Feature modules
<feature>/
dialogs/ # Feature-specific dialogs
helper/ # Feature-specific utilities
models/ # Data models and types
providers/ # Cubits (state management)
screens/ # Full-page widgets
services/ # Business logic
widgets/ # Reusable UI components
shared/ # Shared UI components
ui/
buttons/
cards/
elements/
main.dart
- Follow the Effective Dart style guide
- Use
dart formatfor consistent formatting - Prefer
constconstructors wherever possible - Avoid deeply nested widget trees - extract private widgets or helper methods
| Type | Convention | Example |
|---|---|---|
| Cubits | <Feature>Cubit |
ServerBrowserCubit |
| Cubit States | <Feature>State, <Feature><Variant> |
KyberStatusState, KyberStatusPlaying |
| Services | <Feature>Service |
ModService, VoipService |
| Helpers | <Feature>Helper |
MaximaHelper, StorageHelper |
| Private Widgets | _<Widget> |
_HeaderBar, _StatusWidget |
| Screens | <Feature> |
ServerBrowser, Settings |
- Place cubits in the
providers/folder with_cubit.dartsuffix - Define state classes in the same file as the cubit
- Keep cubits focused on a single responsibility
- Use
BlocBuilderfor rebuilding UI based on state - Use
BlocListenerfor side effects (navigation, dialogs, etc.) - Access cubits via
context.read<T>()for events,context.watch<T>()for reactive state
- Use GetIt via the global
slinstance - Register services in
injection_container.dart - Use
registerSingletonfor immediate initialization - Use
registerSingletonAsyncfor async initialization - Check registration with
sl.isRegistered<T>()before access when appropriate
- Use generated asset classes from
gen/assets.gen.dart - Access via
Assets.icons.<name>,Assets.images.<name>, etc.