Skip to content

Commit bb1f627

Browse files
authored
Merge pull request #2885 from QuentinGruber/perf/resource-event-throttle
perf(resources): change-gate ResourceEvent sends to stop per-tick spam
2 parents 2a99390 + dd8afb3 commit bb1f627

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

‎src/servers/ZoneServer2016/entities/basefullcharacter.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ export abstract class BaseFullCharacter extends BaseLightweightCharacter {
150150

151151
/** BaseFullCharacter loadout values */
152152
_resources: { [resourceId: number]: number } = {};
153+
/** Last resource value actually sent to clients, per resourceId - used to change-gate ResourceEvent
154+
* spam so a resource is only re-sent when the client-displayed value changes. Reset on respawn/resync. */
155+
_lastSentResources: { [resourceId: number]: number } = {};
153156
_loadout: { [loadoutSlotId: number]: LoadoutItem } = {};
154157
_equipment: { [equipmentSlotId: number]: CharacterEquipment } = {};
155158
_containers: { [loadoutSlotId: number]: LoadoutContainer } = {};

‎src/servers/ZoneServer2016/entities/character.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,8 @@ export class Character2016 extends BaseFullCharacter {
15791579
}
15801580

15811581
resetResources(server: ZoneServer2016) {
1582+
// Full resource resync: clear the change-gate cache so every value below is (re)sent.
1583+
this._lastSentResources = {};
15821584
this._resources[ResourceIds.HEALTH] = 10000;
15831585
this._resources[ResourceIds.STAMINA] = 600;
15841586
this._resources[ResourceIds.BLEEDING] = 0;

‎src/servers/ZoneServer2016/zoneserver.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ const spawnLocations2 = PluginManager.loadServerData(
331331
"2016/sampleData/equipmentModelTexturesMapping.json"
332332
);
333333

334+
// A player resource is only re-sent to clients when it crosses one of these fractions of its MAX_VALUE
335+
// (permille), so continuously-decaying resources no longer emit a ResourceEvent every tick for a change
336+
// the client cannot render. TODO: RE-confirm the client's actual resource-bar granularity.
337+
const RESOURCE_SEND_STEPS = 1000;
338+
334339
export class ZoneServer2016 extends EventEmitter {
335340
/** Networking layer - allows sending game data to the game client,
336341
* lays on top of the H1Z1 protocol (on top of the actual H1Z1 packets)
@@ -4024,13 +4029,47 @@ export class ZoneServer2016 extends EventEmitter {
40244029
}
40254030
}
40264031

4032+
/**
4033+
* Change-gate for ResourceEvent sends. Returns true (and updates the character's last-sent cache) only
4034+
* when the value differs from the last value sent by enough to change what the client displays - killing
4035+
* per-tick spam for continuously-decaying resources whose raw value ticks by a client-imperceptible
4036+
* amount. The client renders resources as a fraction of MAX_VALUE, so the value is quantized to
4037+
* RESOURCE_SEND_STEPS. Non-player entities (vehicles/doors/etc., not in _characters) are never gated, so
4038+
* their existing behavior is unchanged.
4039+
*/
4040+
private _shouldSendResourceEvent(
4041+
entityId: string,
4042+
resourceId: number,
4043+
value: number
4044+
): boolean {
4045+
const character = this._characters[entityId];
4046+
if (!character) return true; // non-player resource - preserve existing behavior
4047+
const v = value >= 0 ? value : 0;
4048+
const last = character._lastSentResources[resourceId];
4049+
const max = this.getResourceMaxValue(resourceId as ResourceIds);
4050+
let changed: boolean;
4051+
if (last === undefined || max <= 0) {
4052+
changed = last !== v;
4053+
} else {
4054+
const quantum = (n: number) =>
4055+
Math.round((n / max) * RESOURCE_SEND_STEPS);
4056+
changed =
4057+
quantum(v) !== quantum(last) ||
4058+
// always emit exact empty/full transitions (drive starving/dehydrated/full states)
4059+
((v <= 0 || v >= max) && v !== last);
4060+
}
4061+
if (changed) character._lastSentResources[resourceId] = v;
4062+
return changed;
4063+
}
4064+
40274065
updateResource(
40284066
client: Client,
40294067
entityId: string,
40304068
value: number,
40314069
resourceId: ResourceIds,
40324070
resourceType?: number // most resources have the same id and type
40334071
) {
4072+
if (!this._shouldSendResourceEvent(entityId, resourceId, value)) return;
40344073
this.sendData<ResourceEvent>(client, "ResourceEvent", {
40354074
eventData: {
40364075
type: 3,
@@ -4051,6 +4090,7 @@ export class ZoneServer2016 extends EventEmitter {
40514090
resourceType: number,
40524091
dictionary: EntityDictionary<BaseEntity>
40534092
) {
4093+
if (!this._shouldSendResourceEvent(entityId, resourceId, value)) return;
40544094
this.sendDataToAllWithSpawnedEntity<ResourceEvent>(
40554095
dictionary,
40564096
entityId,

0 commit comments

Comments
 (0)