Skip to content

Commit

Permalink
Merge pull request #1241 from wowsims/feature/improve-detailed-result…
Browse files Browse the repository at this point in the history
…s-performance

[UI] Feature/improve detailed results performance
  • Loading branch information
1337LutZ authored Jan 4, 2025
2 parents 7a31a51 + d9f9772 commit 64ca4bc
Show file tree
Hide file tree
Showing 21 changed files with 396 additions and 163 deletions.
1 change: 1 addition & 0 deletions proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ message ErrorOutcome {

// RPC RaidSim
message RaidSimRequest {
string request_id = 5;
Raid raid = 1;
Encounter encounter = 2;
SimOptions sim_options = 3;
Expand Down
33 changes: 33 additions & 0 deletions ui/core/cache_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export type CacheHandlerOptions = {
keysToKeep?: number;
};

export class CacheHandler<T> {
keysToKeep: CacheHandlerOptions['keysToKeep'];
private data = new Map<string, T>();

constructor(options: CacheHandlerOptions = {}) {
this.keysToKeep = options.keysToKeep;
}

has(id: string): boolean {
return this.data.has(id);
}

get(id: string): T | undefined {
return this.data.get(id);
}

set(id: string, result: T) {
this.data.set(id, result);
if (this.keysToKeep) this.keepMostRecent();
}

private keepMostRecent() {
if (this.keysToKeep && this.data.size > this.keysToKeep) {
const keys = [...this.data.keys()];
const keysToRemove = keys.slice(0, keys.length - this.keysToKeep);
keysToRemove.forEach(key => this.data.delete(key));
}
}
}
6 changes: 2 additions & 4 deletions ui/core/components/detailed_results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,7 @@ export class EmbeddedDetailedResults extends DetailedResults {
this.tabWindow = window.open(url.href, 'Detailed Results');
this.tabWindow!.addEventListener('load', async () => {
if (this.latestRun) {
await this.updateSettings();
await this.setSimRunData(this.latestRun);
await Promise.all([this.updateSettings(), this.setSimRunData(this.latestRun)]);
}
});
} else {
Expand All @@ -425,8 +424,7 @@ export class EmbeddedDetailedResults extends DetailedResults {
simResultsManager.currentChangeEmitter.on(async () => {
const runData = simResultsManager.getRunData();
if (runData) {
await this.updateSettings();
await this.setSimRunData(runData);
await Promise.all([this.updateSettings(), this.setSimRunData(runData)]);
}
});
}
Expand Down
22 changes: 8 additions & 14 deletions ui/core/components/detailed_results/log_runner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ref } from 'tsx-vanilla';

import { SimLog } from '../../proto_utils/logs_parser';
import { TypedEvent } from '../../typed_event.js';
import { fragmentToString } from '../../utils';
import { BooleanPicker } from '../pickers/boolean_picker.js';
import { ResultComponent, ResultComponentConfig, SimResultData } from './result_component.js';
export class LogRunner extends ResultComponent {
Expand All @@ -18,7 +19,7 @@ export class LogRunner extends ResultComponent {
contentContainer: HTMLTableSectionElement;
};
cacheOutput: {
cacheKey: number | null;
cacheKey: string | null;
logs: SimLog[] | null;
logsAsHTML: Element[] | null;
logsAsText: string[] | null;
Expand Down Expand Up @@ -132,41 +133,34 @@ export class LogRunner extends ResultComponent {
}

onSimResult(resultData: SimResultData): void {
this.getLogs(resultData)
this.searchLogs(this.ui.search.value)
this.getLogs(resultData);
this.searchLogs(this.ui.search.value);
}

getLogs(resultData: SimResultData) {
if (!resultData) return [];
if (this.cacheOutput.cacheKey === resultData?.eventID) {
const cacheKey = resultData.result.request.requestId;
if (this.cacheOutput.cacheKey === cacheKey) {
return this.cacheOutput.logsAsHTML;
}

const validLogs = resultData.result.logs.filter(log => !log.isCastCompleted());
this.cacheOutput.cacheKey = resultData?.eventID;
this.cacheOutput.cacheKey = cacheKey;
this.cacheOutput.logs = validLogs;
this.cacheOutput.logsAsHTML = validLogs.map(log => this.renderItem(log));
this.cacheOutput.logsAsText = this.cacheOutput.logsAsHTML.map(element => fragmentToString(element).trim().toLowerCase());

return this.cacheOutput.logsAsHTML;
}

renderItem(log: SimLog) {
return (
<tr>
<td className="log-timestamp">{log.formattedTimestamp()}</td>
<td className="log-evdsfent">{log.toHTML(false)}</td>
<td className="log-evdsfent">{log.toHTML(false).cloneNode(true)}</td>
</tr>
) as HTMLTableRowElement;
}
}

const fragmentToString = (element: Node | Element) => {
const div = document.createElement('div');
div.appendChild(element.cloneNode(true));
return div.innerHTML;
};

class CustomVirtualScroll {
private scrollContainer: HTMLElement;
private contentContainer: HTMLElement;
Expand Down
44 changes: 28 additions & 16 deletions ui/core/components/detailed_results/metrics_table/metrics_table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import tippy from 'tippy.js';
import { ref } from 'tsx-vanilla';

import { CacheHandler } from '../../../cache_handler';
import { TOOLTIP_METRIC_LABELS } from '../../../constants/tooltips';
import { ActionId } from '../../../proto_utils/action_id';
import { ActionMetrics, AuraMetrics, ResourceMetrics, UnitMetrics } from '../../../proto_utils/sim_result';
Expand Down Expand Up @@ -29,6 +30,8 @@ export interface MetricsColumnConfig<T> {
fillCell?: (metric: T, cellElem: HTMLElement, rowElem: HTMLElement, isChildRow?: boolean) => void;
}

const cachedMetricsTableIcon = new CacheHandler<HTMLAnchorElement>();

export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitMetrics | ResourceMetrics> extends ResultComponent {
private readonly columnConfigs: Array<MetricsColumnConfig<T>>;

Expand All @@ -50,13 +53,13 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
</table>,
);

this.tableElem = this.rootElem.getElementsByClassName('metrics-table')[0] as HTMLTableSectionElement;
this.bodyElem = this.rootElem.getElementsByClassName('metrics-table-body')[0] as HTMLElement;
this.tableElem = this.rootElem.querySelector<HTMLTableSectionElement>('.metrics-table')!;
this.bodyElem = this.rootElem.querySelector<HTMLTableSectionElement>('.metrics-table-body')!;

const headerRowElem = this.rootElem.getElementsByClassName('metrics-table-header-row')[0] as HTMLElement;
const headerRowElem = this.rootElem.querySelector<HTMLTableRowElement>('.metrics-table-header-row')!;
this.columnConfigs.forEach(columnConfig => {
const headerCell = document.createElement('th');
const tooltip = columnConfig.tooltip || TOOLTIP_METRIC_LABELS[columnConfig.name as keyof typeof TOOLTIP_METRIC_LABELS];
const tooltipContent = columnConfig.tooltip || TOOLTIP_METRIC_LABELS[columnConfig.name as keyof typeof TOOLTIP_METRIC_LABELS];
headerCell.classList.add('metrics-table-header-cell');
if (columnConfig.columnClass) {
headerCell.classList.add(...columnConfig.columnClass.split(' '));
Expand All @@ -65,11 +68,12 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
headerCell.classList.add(...columnConfig.headerCellClass.split(' '));
}
headerCell.appendChild(<span>{columnConfig.name}</span>);
if (tooltip) {
tippy(headerCell, {
content: tooltip,
if (tooltipContent) {
const tooltip = tippy(headerCell, {
content: tooltipContent,
ignoreAttributes: true,
});
this.addOnResetCallback(() => tooltip.destroy());
}
headerRowElem.appendChild(headerCell);
});
Expand Down Expand Up @@ -158,21 +162,26 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
}

onSimResult(resultData: SimResultData) {
this.bodyElem.textContent = '';
this.reset();
const groupedMetrics = this.getGroupedMetrics(resultData).filter(group => group.length > 0);
if (groupedMetrics.length == 0) {
if (groupedMetrics.length) {
this.rootElem.classList.remove('hide');
} else {
this.rootElem.classList.add('hide');
this.onUpdate.emit(resultData.eventID);
return;
} else {
this.rootElem.classList.remove('hide');
}

groupedMetrics.forEach(group => this.addGroup(group));
$(this.tableElem).trigger('update');
this.onUpdate.emit(resultData.eventID);
}

reset() {
super.reset();
this.bodyElem.replaceChildren();
}

// Whether a single-element group should have its parent row removed.
// Override this to add custom behavior.
protected shouldCollapse(metric: T): boolean {
Expand Down Expand Up @@ -203,20 +212,23 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
name: 'Name',
fillCell: (metric: T, cellElem: HTMLElement, rowElem: HTMLElement) => {
const data = getData(metric);
const iconElem = ref<HTMLAnchorElement>();
const actionIdAsString = data.actionId.toString();
const iconElemRef = ref<HTMLAnchorElement>();
const iconElem = cachedMetricsTableIcon.get(actionIdAsString);
cellElem.appendChild(
<div className="metrics-action">
<a ref={iconElem} className="metrics-action-icon"></a>
{iconElem?.cloneNode() || <a ref={iconElemRef} className="metrics-action-icon"></a>}
<span className="metrics-action-name text-truncate">{data.name}</span>
<span className="expand-toggle fa fa-caret-right"></span>
<span className="expand-toggle fa fa-caret-down"></span>
</div>,
);
if (iconElem.value) {
data.actionId.setBackgroundAndHref(iconElem.value);
data.actionId.setWowheadDataset(iconElem.value, {
if (!iconElem && iconElemRef.value) {
data.actionId.setBackgroundAndHref(iconElemRef.value);
data.actionId.setWowheadDataset(iconElemRef.value, {
useBuffAura: data.metricType === 'AuraMetrics',
});
cachedMetricsTableIcon.set(actionIdAsString, iconElemRef.value);
}
},
};
Expand Down
2 changes: 1 addition & 1 deletion ui/core/components/detailed_results/player_damage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class PlayerDamageMetricsTable extends MetricsTable<UnitMetrics> {

customizeRowElem(player: UnitMetrics, rowElem: HTMLElement) {
rowElem.classList.add('player-damage-row');
rowElem.addEventListener('click', event => {
rowElem.addEventListener('click', () => {
this.resultsFilter.setPlayer(this.getLastSimResult().eventID, player.index);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class PlayerDamageTakenMetricsTable extends MetricsTable<UnitMetrics> {

customizeRowElem(player: UnitMetrics, rowElem: HTMLElement) {
rowElem.classList.add('player-damage-row');
rowElem.addEventListener('click', event => {
rowElem.addEventListener('click', () => {
this.resultsFilter.setPlayer(this.getLastSimResult().eventID, player.index);
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TOOLTIP_METRIC_LABELS } from '../../constants/tooltips';
import { ResourceType } from '../../proto/api';
import { resourceNames } from '../../proto_utils/names';
import { ResourceMetrics } from '../../proto_utils/sim_result';
Expand All @@ -12,14 +11,14 @@ export class ResourceMetricsTable extends ResultComponent {
super(config);

orderedResourceTypes.forEach(resourceType => {
const containerElem = document.createElement('div');
containerElem.classList.add('resource-metrics-table-container', 'hide');
containerElem.innerHTML = `<span class="resource-metrics-table-title">${resourceNames.get(resourceType)}</span>`;
const containerElem = (
<div className="resource-metrics-table-container hide">
<span className="resource-metrics-table-title">{resourceNames.get(resourceType)}</span>
</div>
) as HTMLElement;
this.rootElem.appendChild(containerElem);

const childConfig = config;
childConfig.parent = containerElem;
const table = new TypedResourceMetricsTable(childConfig, resourceType);
const table = new TypedResourceMetricsTable({ ...config, parent: containerElem }, resourceType);
table.onUpdate.on(() => {
if (table.rootElem.classList.contains('hide')) {
containerElem.classList.add('hide');
Expand Down
33 changes: 21 additions & 12 deletions ui/core/components/detailed_results/result_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,36 @@ import { SimResult, SimResultFilter } from '../../proto_utils/sim_result.js';
import { EventID, TypedEvent } from '../../typed_event.js';

export interface SimResultData {
eventID: EventID,
result: SimResult,
filter: SimResultFilter,
};
eventID: EventID;
result: SimResult;
filter: SimResultFilter;
}

export interface ResultComponentConfig {
parent: HTMLElement,
rootCssClass?: string,
cssScheme?: string | null,
resultsEmitter: TypedEvent<SimResultData | null>,
};
parent: HTMLElement;
rootCssClass?: string;
cssScheme?: string | null;
resultsEmitter: TypedEvent<SimResultData | null>;
}

export abstract class ResultComponent extends Component {
lastSimResult: SimResultData | null;
private resetCallbacks: (() => void)[] = [];

constructor(config: ResultComponentConfig) {
super(config.parent, config.rootCssClass || 'result-component');
this.lastSimResult = null;

config.resultsEmitter.on((_, resultData) => {
if (!resultData)
return;
if (!resultData) return;

this.lastSimResult = resultData;
this.onSimResult(resultData);
});
}

hasLastSimResult(): boolean {
return this.lastSimResult != null;
return !!this.lastSimResult;
}

getLastSimResult(): SimResultData {
Expand All @@ -44,4 +44,13 @@ export abstract class ResultComponent extends Component {
}

abstract onSimResult(resultData: SimResultData): void;

addOnResetCallback(callback: () => void) {
this.resetCallbacks.push(callback);
}

reset() {
this.resetCallbacks.forEach(callback => callback());
this.resetCallbacks = [];
}
}
10 changes: 5 additions & 5 deletions ui/core/components/detailed_results/results_filter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { UnitPicker, UnitValue, UnitValueConfig } from '../pickers/unit_picker.jsx';
import { UnitReference, UnitReference_Type as UnitType } from '../../proto/common.js';
import { SimResult, SimResultFilter } from '../../proto_utils/sim_result.js';
import { EventID, TypedEvent } from '../../typed_event.js';
import { ResultComponent, ResultComponentConfig, SimResultData } from './result_component.js';
import { UnitReference, UnitReference_Type as UnitType } from '../../proto/common';
import { SimResult, SimResultFilter } from '../../proto_utils/sim_result';
import { EventID, TypedEvent } from '../../typed_event';
import { UnitPicker, UnitValue, UnitValueConfig } from '../pickers/unit_picker';
import { ResultComponent, ResultComponentConfig, SimResultData } from './result_component';

const ALL_UNITS = -1;

Expand Down
Loading

0 comments on commit 64ca4bc

Please sign in to comment.