Fix performance on long battles

1000-turn battles often took over a minute to load, but they should now
be loaded in a few seconds. In addition, loading no longer freezes tabs,
and skipping several turns back should be a lot more performant.

There are two tricks here:

1. Every 300ms, we rest for 1ms to let the event loop run, which doesn't
   provide _that_ much UI responsiveness, but enough for the tab not to
   freeze entirely, and allows things like leaving the battle or clicking
   "Prev turn" multiple times in a row.

2. Instead of writing every single turn to the battle log when skipping
   to the end of a replay (such as when joining a battle), we only write
   the most recent 100 turns. This drastically speeds up loading.
This commit is contained in:
Guangcong Luo 2023-03-27 04:42:46 -04:00
parent 7ba9053742
commit fc00e68231
3 changed files with 55 additions and 6 deletions

View File

@ -176,14 +176,14 @@ var Replays = {
this.battle.reset();
},
ff: function () {
this.battle.skipTurn();
this.battle.seekBy(1);
},
rewind: function () {
this.battle.seekTurn(this.battle.turn - 1);
this.battle.seekBy(-1);
},
ffto: function () {
var turn = prompt('Turn?');
if (!turn.trim()) return;
if (!turn || !turn.trim()) return;
if (turn === 'e' || turn === 'end' || turn === 'f' || turn === 'finish') turn = Infinity;
turn = Number(turn);
if (isNaN(turn) || turn < 0) alert("Invalid turn");

View File

@ -29,6 +29,7 @@ export class BattleLog {
scene: BattleScene | null = null;
preemptElem: HTMLDivElement = null!;
atBottom = true;
skippedLines = false;
className: string;
battleParser: BattleTextParser | null = null;
joinLeave: {
@ -78,12 +79,42 @@ export class BattleLog {
reset() {
this.innerElem.innerHTML = '';
this.atBottom = true;
this.skippedLines = false;
}
destroy() {
this.elem.onscroll = null;
}
addSeekEarlierButton() {
if (this.skippedLines) return;
this.skippedLines = true;
const el = document.createElement('div');
el.className = 'chat';
el.innerHTML = '<button class="button earlier-button"><i class="fa fa-caret-up"></i><br />Earlier messages</button>';
const button = el.getElementsByTagName('button')[0];
button?.addEventListener?.('click', e => {
e.preventDefault();
this.scene?.battle.seekTurn(this.scene.battle.turn - 100);
});
this.addNode(el);
}
add(args: Args, kwArgs?: KWArgs, preempt?: boolean) {
if (kwArgs?.silent) return;
if (this.scene?.battle.seeking) {
const battle = this.scene.battle;
if (battle.stepQueue.length > 2000) {
// adding elements gets slower and slower the more there are
// (so showing 100 turns takes around 2 seconds, and 1000 turns takes around a minute)
// capping at 100 turns makes everything _reasonably_ snappy
if (
battle.seeking === Infinity ?
battle.currentStep < battle.stepQueue.length - 2000 :
battle.turn < battle.seeking! - 100
) {
this.addSeekEarlierButton();
return;
}
}
}
let divClass = 'chat';
let divHTML = '';
let noNotify: boolean | undefined;

View File

@ -3734,13 +3734,19 @@ export class Battle {
this.subscription?.('playing');
}
skipTurn() {
this.seekTurn(this.turn + 1);
this.seekBy(1);
}
seekBy(deltaTurn: number) {
if (this.seeking === Infinity && deltaTurn < 0) {
return this.seekTurn(this.turn + 1);
}
this.seekTurn((this.seeking ?? this.turn) + deltaTurn);
}
seekTurn(turn: number, forceReset?: boolean) {
if (isNaN(turn)) return;
turn = Math.max(Math.floor(turn), 0);
if (this.seeking !== null && this.seeking > turn && !forceReset) {
if (this.seeking !== null && turn > this.turn && !forceReset) {
this.seeking = turn;
return;
}
@ -3779,9 +3785,11 @@ export class Battle {
nextStep() {
if (!this.shouldStep()) return;
let time = Date.now();
this.scene.startAnimations();
let animations = undefined;
let interruptionCount: number;
do {
this.waitForAnimations = true;
if (this.currentStep >= this.stepQueue.length) {
@ -3802,6 +3810,16 @@ export class Battle {
} else if (this.waitForAnimations === 'simult') {
this.scene.timeOffset = 0;
}
if (Date.now() - time > 300) {
interruptionCount = this.scene.interruptionCount;
setTimeout(() => {
if (interruptionCount === this.scene.interruptionCount) {
this.nextStep();
}
}, 1);
return;
}
} while (!animations && this.shouldStep());
if (this.paused && this.turn >= 0 && this.seeking === null) {
@ -3812,7 +3830,7 @@ export class Battle {
if (!animations) return;
const interruptionCount = this.scene.interruptionCount;
interruptionCount = this.scene.interruptionCount;
animations.done(() => {
if (interruptionCount === this.scene.interruptionCount) {
this.nextStep();