From e36d6566c7480bc362a7630d805440d06a421924 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 18:18:17 +0000 Subject: [PATCH] Cut party UI redraws while a party member is in combat A party member's health percentage changed on nearly every 500 ms tick during combat (damage, then regeneration), and every update rebuilt the party list and made the overlay repaint. Each overlay repaint copies the whole screen into a texture, which shows up as a periodic frametime spike. - Round the sent health percentage up to a whole number, so it only goes out when the bar visibly changes and 0 still means dead. - Ignore health updates that don't change a member's value, and only flash the party list on a real change. - Track party members by id so their elements are reused, and drop the width transition, which would otherwise repaint for 0.8 s per update. See tiltedphoques/TiltedEvolution#940. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018fvQxPkaY5xU6UUaRU3L8h --- Code/client/Services/Generic/OverlayService.cpp | 7 ++++++- .../src/app/components/group/group.component.html | 2 +- .../src/app/components/group/group.component.scss | 1 - Code/skyrim_ui/src/app/components/group/group.component.ts | 6 +++++- Code/skyrim_ui/src/app/services/client.service.ts | 4 +++- Code/skyrim_ui/src/app/services/group.service.ts | 7 ++++++- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Code/client/Services/Generic/OverlayService.cpp b/Code/client/Services/Generic/OverlayService.cpp index 50f2b3a734..8c9a9fce18 100644 --- a/Code/client/Services/Generic/OverlayService.cpp +++ b/Code/client/Services/Generic/OverlayService.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include @@ -491,7 +493,10 @@ void OverlayService::RunPlayerHealthUpdates() noexcept static float s_previousPercentage = -1.f; - const float newPercentage = CalculateHealthPercentage(PlayerCharacter::Get()); + // The party UI shows a bar, so whole percents are enough. Round up so that 0 still means dead. + // Without rounding, regeneration in combat changes the value on every tick and each update + // makes the overlay redraw. + const float newPercentage = std::ceil(CalculateHealthPercentage(PlayerCharacter::Get())); if (newPercentage == s_previousPercentage) return; diff --git a/Code/skyrim_ui/src/app/components/group/group.component.html b/Code/skyrim_ui/src/app/components/group/group.component.html index 522ea6325f..4f64c96989 100644 --- a/Code/skyrim_ui/src/app/components/group/group.component.html +++ b/Code/skyrim_ui/src/app/components/group/group.component.html @@ -5,7 +5,7 @@ [ngStyle]="positionStyle | async" >
{ this.flashGroup(); @@ -210,6 +210,10 @@ export class GroupComponent implements OnInit, OnDestroy { } } + public trackByMemberId(_index: number, member: Player) { + return member.id; + } + public invite(playerId: number) { this.groupService.invite(playerId); } diff --git a/Code/skyrim_ui/src/app/services/client.service.ts b/Code/skyrim_ui/src/app/services/client.service.ts index cb69624922..c9b9e620ed 100644 --- a/Code/skyrim_ui/src/app/services/client.service.ts +++ b/Code/skyrim_ui/src/app/services/client.service.ts @@ -484,7 +484,9 @@ export class ClientService implements OnDestroy { private onSetHealth(playerId: number, health: number) { this.zone.run(() => { - this.healthChange.next(new Player({ id: playerId, health: health })); + this.healthChange.next( + new Player({ id: playerId, health: Math.ceil(health) }), + ); }); } diff --git a/Code/skyrim_ui/src/app/services/group.service.ts b/Code/skyrim_ui/src/app/services/group.service.ts index 706a19f93b..539b628d40 100644 --- a/Code/skyrim_ui/src/app/services/group.service.ts +++ b/Code/skyrim_ui/src/app/services/group.service.ts @@ -6,6 +6,7 @@ import { Observable, of, pluck, + Subject, Subscription, } from 'rxjs'; import { filter, map } from 'rxjs/operators'; @@ -25,6 +26,9 @@ import { Sound, SoundService } from './sound.service'; export class GroupService implements OnDestroy { public group = new BehaviorSubject(undefined); + /** Emits the id of a party member whose health value actually changed. */ + public memberHealthChange = new Subject(); + private debugSubscription: Subscription; private connectionSubscription: Subscription; private userHealthSubscription: Subscription; @@ -102,9 +106,10 @@ export class GroupService implements OnDestroy { if (group) { const foundPlayer = this.playerListService.getPlayerById(player.id); - if (foundPlayer) { + if (foundPlayer && foundPlayer.health !== player.health) { foundPlayer.health = player.health; this.updateGroup(); + this.memberHealthChange.next(foundPlayer.id); } } },