Hyprland icon indicating copy to clipboard operation
Hyprland copied to clipboard

refactor: only include what you use

Open memchr opened this issue 2 years ago • 9 comments

Currently, header usage in Hyprland is rather chaotic, some headers have circular dependencies, some headers or source includes have more than they actually need.

This can cause unexpected compiler/linker errors and increase maintenance costs. It also slows down PCH disabled and incremental builds. Since unnecessary files are included, the build system will recompile where it's not necessary.

Another problem is with the development tools, especially the language servers such as clangd, where it reports mysterious errors in headers included in headers included in headers included in headers included in headers...

Thus this PR propose that each source file should contain only and directly what it uses.

Advantages

Reduces incremental build time

For example, suppose we do an initial build with PCH disabled (required for compile caching), then modify KeybindManager.hpp and corresponding .cpp to introduce a new dispatcher, and finally incrementally rebuild the modified files.

  • main branch:
    • 49 files to rebuild (67 total)
    • cpu time: avg 120s (single thread)
  • PR:
    • 12 files to rebuild
    • cpu time: avg 33.3s (single thread)

Speed up: avg 350%

Clearly defined dependencies

Continuing with the example above, in the main branch it is unclear how many files depend on KeybindManager, even though there are only three instances of `#include "KeybindManager.hpp", a change in the header results in in 49 modified files.

In this PR, the dependant of KeybindManager can be easly found using a simple grep [^1],

$ grep KeybindManager.hpp -lR src | grep -v pch
src/config/ConfigManager.cpp
src/debug/HyprCtl.cpp
src/events/Windows.cpp
src/helpers/Monitor.cpp
src/layout/DwindleLayout.cpp
src/layout/MasterLayout.cpp
src/layout/IHyprLayout.cpp
src/managers/input/InputManager.cpp
src/managers/KeybindManager.cpp
src/plugins/PluginAPI.cpp
src/Window.cpp
src/Compositor.cpp

[^1]: Some are not hard dependencies and further improvements can be made.

memchr avatar Sep 22 '23 17:09 memchr

yes. The problem with this is image

idk about the "chaotic" part, if I have 30 includes on top of every file, to me, that is even more chaotic.

vaxerski avatar Sep 22 '23 18:09 vaxerski

We could absolutely eliminate almost all the includes in each cpp file by just including a single pch.hpp file that includes everything else, but that would complicate the dependency. It is chaotic because of how the individual units are linked together.

e.g.

ninja -C build -t graph

memchr avatar Sep 22 '23 18:09 memchr

If lines of code is important as a metric, I think another approach is to include all the use std and dependency library headers into a single common header and include it in each required file instead.

But separating the headers owned by Hyprland could still be valuable for development, as it means fewer rebuilds and fewer complaints from the language server.

memchr avatar Sep 22 '23 18:09 memchr

well, including all the stl shit into a common.hpp file would be bad the other way - increase compile times massively. Up till now I have been trying to strike a balance between the two.

vaxerski avatar Sep 22 '23 18:09 vaxerski

There is another more conservative branch which only touches headers from /src, the diff should be half this size.

memchr avatar Sep 22 '23 18:09 memchr

where

vaxerski avatar Sep 22 '23 22:09 vaxerski

wait, since this is just a util, can't we make it run before build, write the new source files to ./build and compile that? I believe CMake would not have issues with that

vaxerski avatar Sep 24 '23 22:09 vaxerski

You mean something like LLVM's clang-include-cleaner/fixer or Google's iwyu? Both run as slow as C++ compilers, if not slower.

memchr avatar Sep 25 '23 03:09 memchr

hm, never mind then, sucks.

vaxerski avatar Sep 25 '23 09:09 vaxerski

Just personal opinion, I believe the best practice is still to include only and directly what it uses. Maybe step by step, but new include should follow the rule.

Share common includes in several headers maybe good enough at the beginning, but it's likely to cause more trouble as time goes by.

Tens of includes on top of every file may be chaotic to some degree, but it's still necessary code to keep things explicit and straight forward. Just like the code for other functions or classes, it's there for a reason, and can be ignored too when you're focused on something else.

rtgiskard avatar May 21 '24 05:05 rtgiskard