Skip to content

Commit

Permalink
real-time update of the current run
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-vorobiov committed Dec 22, 2024
1 parent 1416876 commit 9dceb72
Show file tree
Hide file tree
Showing 19 changed files with 417 additions and 158 deletions.
2 changes: 1 addition & 1 deletion evolve_analytics.meta.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ==UserScript==
// @name Evolve Analytics
// @namespace http://tampermonkey.net/
// @version 0.9.3
// @version 0.10.0
// @description Track and see detailed information about your runs
// @author Sneed
// @match https://pmotschmann.github.io/Evolve/
Expand Down
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type ViewConfig = {
resetType: keyof typeof resets,
universe?: keyof typeof universes,
mode: keyof typeof viewModes,
includeCurrentRun?: boolean,
smoothness: number,
showBars: boolean,
showLines: boolean,
Expand Down Expand Up @@ -129,7 +130,13 @@ export class ConfigManager extends Subscribable {
return this.config.lastOpenViewIndex;
}

onViewOpened(view: View) {
get openView() {
if (this.openViewIndex !== undefined) {
return this.views[this.openViewIndex];
}
}

viewOpened(view: View) {
const idx = this.views.indexOf(view);
this.config.lastOpenViewIndex = idx === -1 ? undefined : idx;

Expand All @@ -141,6 +148,7 @@ export class ConfigManager extends Subscribable {
const view: ViewConfig = {
resetType: "ascend",
universe: this.game.universe,
includeCurrentRun: false,
mode: "timestamp",
showBars: true,
showLines: false,
Expand Down
156 changes: 111 additions & 45 deletions src/exports/plotPoints.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { generateMilestoneNames } from "../milestones";
import { transformMap, zip } from "../utils";
import type { HistoryManager, HistoryEntry, MilestoneReference } from "../history";
import { zip } from "../utils";
import type { HistoryManager, HistoryEntry } from "../history";
import type { ViewConfig } from "../config";
import type { LatestRun } from "../runTracking";

export type PlotPoint = {
run: number,
milestone: string,
day: number,
dayDiff?: number, // days since the last enabled non-event milestone
segment: number, // days since the last non-event milestone
raceName?: string
raceName?: string,
pending?: boolean
}

function makeMilestoneNamesMapping(history: HistoryManager, view: ViewConfig): Record<number, string> {
Expand All @@ -20,71 +22,135 @@ function makeMilestoneNamesMapping(history: HistoryManager, view: ViewConfig): R
return Object.fromEntries(zip(milestoneIDs, milestoneNames));
}

export function asPlotPoints(filteredRuns: HistoryEntry[], history: HistoryManager, view: ViewConfig): PlotPoint[] {
const milestones = transformMap(view.milestones, ([milestone, enabled]) => {
return [history.getMilestoneID(milestone), { enabled, isEvent: milestone.startsWith("event:") }];
});
class SegmentCounter {
private previousDay = 0;
private previousEnabledDay = 0;

const milestoneNames = makeMilestoneNamesMapping(history, view);
constructor(private view: ViewConfig) {}

const entries: PlotPoint[] = [];
reset() {
this.previousDay = 0;
this.previousEnabledDay = 0;
}

for (let i = 0; i !== filteredRuns.length; ++i) {
const run = filteredRuns[i];
next(milestone: string, day: number) {
const dayDiff = day - this.previousEnabledDay;
const segment = day - this.previousDay;

// Events have their separate segmentation logic
const events: MilestoneReference[] = [];
const nonEvents: MilestoneReference[] = [];
const isEvent = milestone.startsWith("event:");
const enabled = this.view.milestones[milestone];

for (const [milestoneID, day] of run.milestones) {
if (!(milestoneID in milestones)) {
continue;
}
if (!isEvent) {
this.previousDay = day;

if (milestones[milestoneID].isEvent) {
events.push([milestoneID, day]);
}
else {
nonEvents.push([milestoneID, day]);
if (enabled) {
this.previousEnabledDay = day;
}
}

for (const [milestoneID, day] of events) {
if (!milestones[milestoneID].enabled) {
continue;
}
if (enabled) {
return {
dayDiff: isEvent ? undefined : dayDiff,
segment: isEvent ? day : segment
};
}
}
}

entries.push({
run: i,
raceName: run.raceName,
milestone: milestoneNames[milestoneID],
day,
segment: day
});
export function runAsPlotPoints(currentRun: LatestRun, view: ViewConfig, runIdx: number, orderedMilestones: string[]): PlotPoint[] {
const milestoneNames = generateMilestoneNames(orderedMilestones, view.universe);

const entries: PlotPoint[] = [];

const counter = new SegmentCounter(view);

let nextMilestoneIdx = 0;
for (let i = 0; i !== orderedMilestones.length; ++i) {
const milestone = orderedMilestones[i];
const milestoneName = milestoneNames[i];

const day = currentRun.milestones[milestone];
if (day === undefined) {
continue;
}

let previousDay = 0;
let previousEnabledDay = 0;
nextMilestoneIdx = i + 1;

for (const [milestoneID, day] of nonEvents) {
const dayDiff = day - previousEnabledDay;
const segment = day - previousDay;
const info = counter.next(milestone, day);
if (info === undefined) {
continue;
}

entries.push({
run: runIdx,
raceName: currentRun.raceName,
milestone: milestoneName,
day,
dayDiff: info.dayDiff,
segment: info.segment
});
}

// Guess what the next milestone is gonna be, default to the view's reset
let milestone = `reset:${view.resetType}`;
for (; nextMilestoneIdx !== orderedMilestones.length; ++nextMilestoneIdx) {
const candidate = orderedMilestones[nextMilestoneIdx];
if (!candidate.startsWith("event:") && view.milestones[candidate]) {
milestone = candidate;
break;
}
}

const info = counter.next(milestone, currentRun.totalDays);
if (info === undefined) {
return entries;
}

entries.push({
run: runIdx,
raceName: currentRun.raceName,
milestone: generateMilestoneNames([milestone], view.universe)[0],
day: currentRun.totalDays,
dayDiff: info.dayDiff,
segment: info.segment,
pending: true
});

return entries;
}

previousDay = day;
export function asPlotPoints(filteredRuns: HistoryEntry[], history: HistoryManager, view: ViewConfig): PlotPoint[] {
const milestoneNames = makeMilestoneNamesMapping(history, view);

const entries: PlotPoint[] = [];

if (!milestones[milestoneID].enabled) {
const counter = new SegmentCounter(view);

for (let i = 0; i !== filteredRuns.length; ++i) {
const run = filteredRuns[i];

counter.reset();

for (const [milestoneID, day] of run.milestones) {
const milestone = history.getMilestone(milestoneID);
const milestoneName = milestoneNames[milestoneID];

if (!(milestone in view.milestones)) {
continue;
}

previousEnabledDay = day;
const info = counter.next(milestone, day);
if (info === undefined) {
continue;
}

entries.push({
run: i,
raceName: run.raceName,
milestone: milestoneNames[milestoneID],
milestone: milestoneName,
day,
dayDiff,
segment
dayDiff: info.dayDiff,
segment: info.segment
});
}
}
Expand Down
21 changes: 18 additions & 3 deletions src/game.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { resets } from "./enums";
import { transformMap } from "./utils";
import { Subscribable } from "./subscribable";
import type { Evolve, BuildingInfoTabs, ArpaInfoTab } from "./evolve";

import type { default as JQuery } from "jquery";

declare const $: typeof JQuery;

export class Game {
constructor(private evolve: Evolve) {}
export class Game extends Subscribable {
private subscribed = false;

constructor(private evolve: Evolve) {
super();
}

get runNumber() {
return this.evolve.global.stats.reset + 1;
Expand Down Expand Up @@ -53,12 +58,22 @@ export class Game {
}

onGameDay(fn: (day: number) => void) {
this.on("newDay", fn);

if (!this.subscribed) {
this.subscribeToGameUpdates();
this.subscribed = true;
}
}

private subscribeToGameUpdates() {
let previousDay: number | null = null;
this.onGameTick(() => {
const day = this.day;

if (previousDay !== day) {
fn(day);
this.emit("newDay", this.day);

previousDay = day;
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ const history = initializeHistory(game, config);

processLatestRun(game, config, history);

trackMilestones(game, config);
const currentRun = trackMilestones(game, config);

bootstrapUIComponents(config, history);
bootstrapUIComponents(game, config, history, currentRun);
2 changes: 1 addition & 1 deletion src/milestones.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function milestoneName(milestone: string, universe?: keyof typeof univers
export function generateMilestoneNames(milestones: string[], universe?: keyof typeof universes): string[] {
const candidates: Record<string, [number, string, boolean][]> = {};

for (let i = 0; i != milestones.length; ++i) {
for (let i = 0; i !== milestones.length; ++i) {
const [name, discriminator, force] = milestoneName(milestones[i], universe);
(candidates[name] ??= []).push([i, discriminator, force]);
}
Expand Down
2 changes: 2 additions & 0 deletions src/runTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,6 @@ export function trackMilestones(game: Game, config: ConfigManager) {

saveCurrentRun(currentRunStats);
});

return currentRunStats;
}
17 changes: 11 additions & 6 deletions src/ui/analyticsTab.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { makeViewTab } from "./viewTab";
import { lastChild } from "./utils";
import { weakFor, invokeFor, compose } from "../utils";
import type { Game } from "../game";
import type { ConfigManager, View } from "../config";
import type { HistoryManager } from "../history";
import type { LatestRun } from "../runTracking";

export function buildAnalyticsTab(config: ConfigManager, history: HistoryManager) {
export function buildAnalyticsTab(game: Game, config: ConfigManager, history: HistoryManager, currentRun: LatestRun) {
const tabControlNode = $(`
<li role="tab" aria-controls="analytics-content" aria-selected="true">
<a id="analytics-label" tabindex="0" data-unsp-sanitized="clean">Analytics</a>
Expand Down Expand Up @@ -38,23 +39,27 @@ export function buildAnalyticsTab(config: ConfigManager, history: HistoryManager
const count = controlParentNode.children().length;
const id = `analytics-view-${count}`;

const [controlNode, contentNode] = makeViewTab(id, view, config, history);
const [controlNode, contentNode] = makeViewTab(id, game, view, config, history, currentRun);

controlNode.on("click", () => {
config.onViewOpened(view);
config.viewOpened(view);
});

controlNode.insertBefore(lastChild(analyticsPanel.find("> nav > ul")));
analyticsPanel.append(contentNode);
analyticsPanel.tabs("refresh");
analyticsPanel.tabs({ active: count - 1 });

config.on("viewRemoved", compose([weakFor(view), invokeFor(view)], () => {
config.on("viewRemoved", (removedView) => {
if (removedView !== view) {
return;
}

controlNode.remove();
contentNode.remove();
analyticsPanel.tabs("refresh");
analyticsPanel.tabs({ active: 0 });
}));
});
}

function hidden(node: JQuery) {
Expand Down
Loading

0 comments on commit 9dceb72

Please sign in to comment.