Skip to content

Nit: Const-i-fy FeatureList impl#11223

Merged
strseb merged 2 commits intomainfrom
basti/feature_list_refactor
Apr 28, 2026
Merged

Nit: Const-i-fy FeatureList impl#11223
strseb merged 2 commits intomainfrom
basti/feature_list_refactor

Conversation

@strseb
Copy link
Copy Markdown
Collaborator

@strseb strseb commented Apr 20, 2026

Description

feature.h costs more than it should. Every feature allocates a Featurethat lives in both a hashmap and a QList. Every string field starts with UTF-8 char and gets parsed into to a QString — one temp allocation per field, per feature, at startup.

FeatureList::MaybeInit ends up doing roughly as many temp allocations as the entire MozillaVPN constructor.

before29

Evaluation isn't free either. Feature::isEnabled walks through multiple std::functions, a string-based dependency lookup, and a SettingsHolder round-trip in case Guardian ever flipped the feature. Even a feature hard-wired to
FeatureCallback_false goes through the full pipeline — the compiler can't
prove "always false" because the list is built at runtime.

But most of our flags are hard-coded against compile defines.
We're paying for flexibility we never use, on every start and every check.

My proposal is to swap this into a "Pay What you need" model with 3 Tiers of FeatureFlags:

-> ConstantFeature : Value is known at compile time (Platform::windows, Qt version, build defines). constexpr — zero allocation, fully foldable by the compiler.

-> RuntimeFeature - Value needs a runtime check but never flips (e.g. "disable Windows split-tunnel when Mullvad is detected", Android SDK version). One function-pointer call.

-> Dynamic Feature: OverridableFeature, Dev-menu and Guardian (/api/v1/features) can override. Function-pointer call + settings lookup, only when you opt in.

None of the feature structs are QObjects. For QML, a small FeatureProxy
view-model wraps any AnyFeature reference and exposes the same API QML was
already using — so MZFeatureList.get("foo").isSupported still works
unchanged.


I've split this into 2 Commits to make it nicer to review, part 1 only concerns changes in /feature -
part 2 is a bulk change for consumers of the featurelist.

@strseb strseb requested review from artfwo and encrypt94 and removed request for encrypt94 April 20, 2026 21:45
@strseb strseb marked this pull request as draft April 20, 2026 21:47
@strseb strseb force-pushed the basti/feature_list_refactor branch 6 times, most recently from 727f9a9 to 6ab3b4f Compare April 22, 2026 13:49
The "feature.h" as is is a bit heavy: 
-> We do have a new Allocation of Feature for every feature
-> We do track the pointers in both a hashmap and qlist
-> Each feature QString is created from a UTF-8 char, so each QString needs an allocation too!

Add to that that the evaluation algorightm of feature -> bool (is this on?) - needs to check multiple std::functions 
a String Based List lookup for possible dependencies. A roundtrip to a settingslist to check if we ever feature-flip from guardian.

No optimization is possible, even if we mark a Feature with "FeatureCallback_false" - given we construct the list at runtime, 
fetch a Feature* via a QString and then check a std::func* - these are too many layers for the compiler to determine "this is always false".

The reality is, the majority of our feature-flags are Fixed and Non Changeable by the time the Compiler has evaluated the defines. 
So we are paying a lot to have flexibility we actually never use and we are paying that on every start of the app. 

My proposal is to swap this into a "Pay What you need" model with 3 Tiers of FeatureFlags: 

-> Static Feature: They are determined at compiletime. i.e "is this windows?" They need no allocation, just live in the data section. 
They are constexpr the compiler can do code-elimination on dead codepaths. 

-> Runtime Feature: A wrapper around func -> Bool; They are run on every check, i.e "deactivate windows-split-tunnel when mullvad is detected". 

-> Dynamic Feature: A simplified version of our current Feature*, it has a func->bool for the base value. Overrides via the dev-menu are possibile. 
Also they support overrides from guardian. 



As none of these features are QObjects, for FeatureModel i add a "ViewModel" Class FeatureProxy(feature::any) that can take a ref for any feature and expose 
a Feature to QML. The API for QML is unchanged, only in c++ land we have to change callers.
@strseb strseb force-pushed the basti/feature_list_refactor branch 2 times, most recently from eb7e4aa to 14bbe0b Compare April 23, 2026 13:25
@strseb strseb force-pushed the basti/feature_list_refactor branch from 14bbe0b to 4d78df7 Compare April 23, 2026 13:53
@strseb strseb changed the title Nit: Simplify the FeatureList impl Nit: Const-i-fy FeatureList impl Apr 23, 2026
@strseb strseb marked this pull request as ready for review April 23, 2026 15:34
@strseb strseb requested a review from mcleinman April 23, 2026 15:34
Copy link
Copy Markdown
Collaborator

@mcleinman mcleinman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One tiny tiny nit. Thanks for this.

Comment thread src/feature/features.h
#endif

#ifdef MZ_LINUX
inline constexpr bool linux_ = true;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such a tiny nit, sorry - but this is linux_?

Copy link
Copy Markdown
Collaborator Author

@strseb strseb Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just linux is a reserved keyword in gcc 😰 - i though about gnu_linux, but not sure if editors would
auto suggest that if you type plattform::linux.

@mcleinman mcleinman self-requested a review April 24, 2026 17:18
Copy link
Copy Markdown
Collaborator

@mcleinman mcleinman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was supposed to approval w/ that non-blocking nit. Sorry.

@strseb strseb merged commit c43a9a3 into main Apr 28, 2026
129 of 130 checks passed
@strseb strseb deleted the basti/feature_list_refactor branch April 28, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants