mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 05:30:01 +00:00
cleanup
This commit is contained in:
48
libs/common/src/tools/achievements/achievement-manager.ts
Normal file
48
libs/common/src/tools/achievements/achievement-manager.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Observable, OperatorFunction, map, pipe, withLatestFrom } from "rxjs";
|
||||
|
||||
import { isEarnedEvent } from "./meta";
|
||||
import { AchievementEvent, AchievementValidator } from "./types";
|
||||
import { mapProgressByName } from "./util";
|
||||
|
||||
// computes the list of live achievements; those whose trigger conditions
|
||||
// aren't met are excluded from the active set
|
||||
function active(
|
||||
status$: Observable<AchievementEvent[]>,
|
||||
): OperatorFunction<AchievementValidator[], AchievementValidator[]> {
|
||||
return pipe(
|
||||
withLatestFrom(status$),
|
||||
map(([monitors, log]) => {
|
||||
// partition the log into progress and earned achievements
|
||||
const progressByName = mapProgressByName(log);
|
||||
const earnedByName = new Set(
|
||||
log.filter((e) => isEarnedEvent(e)).map((e) => e.achievement.name),
|
||||
);
|
||||
|
||||
// compute list of active achievements
|
||||
const active = monitors.filter((m) => {
|
||||
// 🧠 the filters could be lifted into a function argument & delivered
|
||||
// as a `Map<FilterType, (monitor) => bool>
|
||||
|
||||
if (m.trigger === "once") {
|
||||
// monitor disabled if already achieved
|
||||
return !earnedByName.has(m.achievement);
|
||||
}
|
||||
|
||||
// monitor disabled if outside of threshold
|
||||
const progress = (m.metric && progressByName.get(m.metric)) || 0;
|
||||
if (progress > (m.trigger.high ?? Number.POSITIVE_INFINITY)) {
|
||||
return false;
|
||||
} else if (progress < (m.trigger.low ?? 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise you're within the threshold, so the monitor is active
|
||||
return true;
|
||||
});
|
||||
|
||||
return active;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export { active };
|
||||
@@ -2,89 +2,43 @@ import { Observable, OperatorFunction, concatMap, from, map, pipe, withLatestFro
|
||||
|
||||
import { EventFormat } from "../log/ecs-format";
|
||||
|
||||
import { active } from "./achievement-manager";
|
||||
import {
|
||||
achievementMonitors$,
|
||||
achievementsLocal$ as achievementsLog$,
|
||||
userActionIn$,
|
||||
} from "./inputs";
|
||||
import { isEarnedEvent, isProgressEvent } from "./meta";
|
||||
import { AchievementEvent, AchievementValidator } from "./types";
|
||||
|
||||
function mapProgressByName(status: AchievementEvent[]) {
|
||||
return new Map(
|
||||
status.filter(isProgressEvent).map((e) => [e.achievement.name, e.achievement.value] as const),
|
||||
);
|
||||
}
|
||||
|
||||
// OPTIMIZATION: compute the list of active monitors from trigger criteria
|
||||
function active(
|
||||
status$: Observable<AchievementEvent[]>,
|
||||
): OperatorFunction<AchievementValidator[], AchievementValidator[]> {
|
||||
return pipe(
|
||||
withLatestFrom(status$),
|
||||
map(([monitors, log]) => {
|
||||
// partition the log into progress and earned achievements
|
||||
const progressByName = mapProgressByName(log);
|
||||
const earnedByName = new Set(
|
||||
log.filter((e) => isEarnedEvent(e)).map((e) => e.achievement.name),
|
||||
);
|
||||
|
||||
// compute list of active achievements
|
||||
const active = monitors.filter((m) => {
|
||||
// 🧠 the filters could be lifted into a function argument & delivered
|
||||
// as a `Map<FilterType, (monitor) => bool>
|
||||
|
||||
if (m.trigger === "once") {
|
||||
// monitor disabled if already achieved
|
||||
return !earnedByName.has(m.achievement);
|
||||
}
|
||||
|
||||
// monitor disabled if outside of threshold
|
||||
const progress = (m.metric && progressByName.get(m.metric)) || 0;
|
||||
if (progress > (m.trigger.high ?? Number.POSITIVE_INFINITY)) {
|
||||
return false;
|
||||
} else if (progress < (m.trigger.low ?? 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise you're within the threshold, so the monitor is active
|
||||
return true;
|
||||
});
|
||||
|
||||
return active;
|
||||
}),
|
||||
);
|
||||
}
|
||||
import { mapProgressByName } from "./util";
|
||||
|
||||
// the formal event processor
|
||||
function validate(
|
||||
monitors$: Observable<AchievementValidator[]>,
|
||||
status$: Observable<AchievementEvent[]>,
|
||||
validators$: Observable<AchievementValidator[]>,
|
||||
captured$: Observable<AchievementEvent[]>,
|
||||
): OperatorFunction<EventFormat, AchievementEvent> {
|
||||
return pipe(
|
||||
withLatestFrom(monitors$),
|
||||
withLatestFrom(validators$),
|
||||
map(([action, monitors]) => {
|
||||
// narrow the list of all live monitors to just those that may produce new logs
|
||||
const triggered = monitors.filter((m) => m.filter(action));
|
||||
return [action, triggered] as const;
|
||||
}),
|
||||
withLatestFrom(status$),
|
||||
concatMap(([[action, monitors], status]) => {
|
||||
withLatestFrom(captured$),
|
||||
concatMap(([[action, validators], captured]) => {
|
||||
const results: AchievementEvent[] = [];
|
||||
const progress = mapProgressByName(captured);
|
||||
|
||||
// process achievement monitors sequentially, accumulating result records
|
||||
for (const monitor of monitors) {
|
||||
const progress = mapProgressByName(status);
|
||||
|
||||
const measured = monitor.measure(action, progress);
|
||||
for (const validator of validators) {
|
||||
const measured = validator.measure(action, progress);
|
||||
results.push(...measured);
|
||||
|
||||
// modify copy produced by filter to avoid reallocation
|
||||
// update progress with the latest measurements
|
||||
for (const m of measured) {
|
||||
progress.set(m.achievement.name, m.achievement.value);
|
||||
}
|
||||
|
||||
results.push(...monitor.earn(measured, progress));
|
||||
const earned = validator.earn(measured, progress);
|
||||
results.push(...earned);
|
||||
}
|
||||
|
||||
// deliver results as a stream containing individual records to maintain
|
||||
@@ -94,6 +48,7 @@ function validate(
|
||||
);
|
||||
}
|
||||
|
||||
// monitors are lazy until their trigger condition is met
|
||||
const liveMonitors$ = achievementMonitors$.pipe(active(achievementsLog$));
|
||||
|
||||
// pre-wired achievement stream; this is the prototype's host, and
|
||||
|
||||
8
libs/common/src/tools/achievements/util.ts
Normal file
8
libs/common/src/tools/achievements/util.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { isProgressEvent } from "./meta";
|
||||
import { AchievementEvent } from "./types";
|
||||
|
||||
export function mapProgressByName(status: AchievementEvent[]) {
|
||||
return new Map(
|
||||
status.filter(isProgressEvent).map((e) => [e.achievement.name, e.achievement.value] as const),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user