Skip to content

Commit

Permalink
Refactor FeatureManager
Browse files Browse the repository at this point in the history
  • Loading branch information
tfedor committed Mar 16, 2024
1 parent 034677f commit 96e410b
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 140 deletions.
8 changes: 3 additions & 5 deletions src/js/Content/Features/Community/ProfileHome/CProfileHome.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Background, ContextType, SteamId} from "../../../modulesContent";
import {Background, ContextType, FeatureManager, SteamId} from "../../../modulesContent";
import {CCommunityBase} from "../CCommunityBase";
import FEarlyAccess from "../../Common/FEarlyAccess";
import FCommunityProfileLinks from "./FCommunityProfileLinks";
Expand Down Expand Up @@ -48,12 +48,10 @@ export class CProfileHome extends CCommunityBase {
FEarlyAccess.show(document.querySelectorAll(".game_info_cap, .showcase_slot:not(.showcase_achievement)"));

// Need to wait on custom background and style (LNY2020 may set the background) to be fetched and set
FPinnedBackground.dependencies = [FCustomBackground, FCustomStyle];
FPinnedBackground.weakDependency = true;
FeatureManager.dependency(FPinnedBackground, [FCustomBackground, true], [FCustomStyle, true]);

// Required for LNY2020 to check whether the profile has a (custom) background
FCustomStyle.dependencies = [FCustomBackground];
FCustomStyle.weakDependency = true;
FeatureManager.dependency(FCustomStyle, [FCustomBackground, true])
}

profileDataPromise() {
Expand Down
8 changes: 3 additions & 5 deletions src/js/Content/Features/Store/App/CApp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {GameId} from "../../../../modulesCore";
import {Background, ContextType} from "../../../modulesContent";
import {Background, ContextType, FeatureManager} from "../../../modulesContent";
import FMediaExpander from "../../Common/FMediaExpander";
import {CStoreBase} from "../Common/CStoreBase";
import FCustomizer from "../Common/FCustomizer";
Expand Down Expand Up @@ -137,12 +137,10 @@ export class CApp extends CStoreBase {
this.data = this.storePageDataPromise().catch(err => { console.error(err); });

// FPackBreakdown skips purchase options with a package info button to avoid false positives
FPackageInfoButton.dependencies = [FPackBreakdown];
FPackageInfoButton.weakDependency = true;
FeatureManager.dependency(FPackageInfoButton, [FPackBreakdown, true]);

// HDPlayer needs to wait for mp4 sources to be set
FHDPlayer.dependencies = [FForceMP4];
FHDPlayer.weakDependency = true;
FeatureManager.dependency(FPackageInfoButton, [FPackBreakdown, true]);
}

storePageDataPromise() {
Expand Down
4 changes: 2 additions & 2 deletions src/js/Content/Features/Store/Wishlist/CWishlist.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {HTMLParser, TimeUtils} from "../../../../modulesCore";
import {ContextType, User} from "../../../modulesContent";
import {ContextType, FeatureManager, User} from "../../../modulesContent";
import {CStoreBase} from "../Common/CStoreBase";
import FAlternativeLinuxIcon from "../Common/FAlternativeLinuxIcon";
import FWishlistHighlights from "./FWishlistHighlights";
Expand Down Expand Up @@ -47,7 +47,7 @@ export class CWishlist extends CStoreBase {
}

// Maintain the order of the buttons
FEmptyWishlist.dependencies = [FExportWishlist];
FeatureManager.dependency(FEmptyWishlist, [FExportWishlist, false]);
}

async applyFeatures() {
Expand Down
4 changes: 2 additions & 2 deletions src/js/Content/Modules/Feature/Feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ class Feature<C extends object = Record<string, never>> {
protected context: C,
) {}

public checkPrerequisites(): boolean {
public checkPrerequisites(): boolean|Promise<boolean> {
return true;
}

public apply(): void {
public apply(): void|Promise<void> {
throw new Error("Stub");
}

Expand Down
126 changes: 0 additions & 126 deletions src/js/Content/Modules/Feature/FeatureManager.js

This file was deleted.

98 changes: 98 additions & 0 deletions src/js/Content/Modules/Feature/FeatureManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type {Feature} from "./Feature";
import {Errors} from "../../../Core/Errors/Errors";

class FeatureManager {

private static featureMap: Map<Function, Feature>;
private static promiseMap: Map<Function, Promise<boolean>>;
private static dependencies: Map<Function, Map<Function, boolean>> = new Map();

private static stats = {
"completed": 0,
"failed": 0,
"dependency": 0,
};

static dependency(dependent: Function, ...dependencies: Array<[Function, boolean]>) {
this.dependencies.set(dependent, new Map(dependencies));
}

static async apply(features: Feature[]) {
this.promiseMap = new Map();
this.featureMap = new Map(features.map(feature => [feature.constructor, feature]));

let promises = features.map(feature => this.getFeaturePromise(feature));
await Promise.allSettled(promises);

console.log(
"Feature loading complete, %i successfully loaded, %i failed to load, %i didn't load due to dependency errors",
this.stats.completed,
this.stats.failed,
this.stats.dependency
);
}

private static getFeaturePromise(feature: Feature): Promise<boolean> {
const func = feature.constructor;

let promise = this.promiseMap.get(func);
if (!promise) {
promise = this.applyInternal(feature);
this.promiseMap.set(func, promise);

}
return promise;
}

private static async applyInternal(feature: Feature): Promise<boolean> {
const func = feature.constructor;

const dependencies = this.dependencies.get(func);
if (dependencies) {
try {
let promises = [];
for (let [dependencyFunc, weak] of dependencies) {
promises.push((async () => {
const dependency = this.featureMap.get(dependencyFunc);
if (!dependency) {
throw new Errors.FeatureDependencyError("Dependency feature not found", dependencyFunc.name);
}
let pass = await this.getFeaturePromise(dependency);
return weak || pass;
})());
}

if (!(await Promise.all(promises)).every(res => res)) {
return false;
}
} catch(e) {
console.warn(
"Not applying feature %s due to an error in the dependency chain",
func.name
);
++this.stats.dependency;
}
}

let pass = Boolean(await feature.checkPrerequisites());
if (pass) {
try {
await feature.apply();
++this.stats.completed;
} catch(e) {
const featureName = func.name;

console.group(featureName);
console.error("Error while applying feature %s", featureName);
console.error(e);
console.groupEnd();

++this.stats.failed;
throw new Errors.FeatureDependencyError("Failed to apply", featureName);
}
}
return pass;
}
}

export {FeatureManager};

0 comments on commit 96e410b

Please sign in to comment.