Refactor: Improve Adjustments loop | Add: Ratelimiting for important Events#1779
Refactor: Improve Adjustments loop | Add: Ratelimiting for important Events#1779mattibat wants to merge 5 commits intoesx-framework:devfrom
Conversation
Added Rate limiting for important events with discord logs
There was a problem hiding this comment.
Pull request overview
This PR refactors client-side “Adjustments” to run frame-based tweaks in a single loop (instead of multiple threads) and introduces server-side per-player/per-event rate limiting with optional Discord logging for key inventory-related events.
Changes:
- Consolidate ammo display, vehicle reward disabling, and density multiplier updates into one client frame loop.
- Add an
IsRateLimitedhelper and apply rate limiting to several ESX inventory/weapon-related server events. - Add a new Discord webhook category (
RateLimit) for rate-limit logs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
[core]/es_extended/shared/config/logs.lua |
Adds a RateLimit webhook entry to support Discord logging for rate-limit events. |
[core]/es_extended/server/main.lua |
Implements rate limiting state + logic, cleanup on disconnect, and integrates checks into multiple net events. |
[core]/es_extended/client/modules/adjustments.lua |
Refactors to a single persistent frame loop via FrameAdjustments() and reuses it from prior entrypoints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local function IsRateLimited(playerId, eventName, maxRequests, windowMs) | ||
| local now = GetGameTimer() | ||
| local playerEvents = EventRateLimit[playerId] | ||
|
|
||
| if not playerEvents then | ||
| playerEvents = {} | ||
| EventRateLimit[playerId] = playerEvents | ||
| end | ||
|
|
||
| local data = playerEvents[eventName] | ||
| if not data or now - data.windowStart > windowMs then | ||
| playerEvents[eventName] = { | ||
| count = 1, |
There was a problem hiding this comment.
GetGameTimer() wraps around (roughly every ~49 days), so now - data.windowStart > windowMs can become negative after wrap and prevent the window from ever resetting (effectively permanent rate-limit). Consider computing elapsed time with wrap-around handling or using a non-wrapping time source (e.g., os.time() with second granularity, or an explicit wrap-safe delta calculation).
| AddEventHandler("playerDropped", function() | ||
| EventRateLimit[source] = nil | ||
| end) |
There was a problem hiding this comment.
EventRateLimit is only cleared on playerDropped. In multichar/logout flows (esx:playerLogout) the player can keep the same source, so rate-limit state can leak across characters and persist unnecessarily. Consider also clearing EventRateLimit[playerId] in the esx:playerLogout handler (and any other unload path).
| if not Config.CustomInventory then | ||
| RegisterNetEvent("esx:updateWeaponAmmo", function(weaponName, ammoCount) | ||
| if Config.EnableRateLimit then | ||
| if IsRateLimited(source, "esx:updateWeaponAmmo", 25, 1000) then | ||
| return | ||
| end | ||
| end | ||
|
|
There was a problem hiding this comment.
Config.EnableRateLimit is referenced but not defined in the shared config files (no occurrence under shared/config). As-is, the feature can’t be enabled via config without users adding a new key themselves. Consider adding Config.EnableRateLimit (default false) to the appropriate config file (likely shared/config/main.lua) and documenting it alongside the other toggles.
| RegisterNetEvent("esx:giveInventoryItem", function(target, itemType, itemName, itemCount) | ||
| if Config.EnableRateLimit then | ||
| if IsRateLimited(source, "esx:giveInventoryItem", 8, 1000) then | ||
| return | ||
| end | ||
| end | ||
|
|
There was a problem hiding this comment.
Rate-limit parameters are duplicated across multiple events (same if Config.EnableRateLimit + IsRateLimited(...) pattern with hardcoded thresholds). Consider centralizing per-event limits in a table/config and/or adding a small helper wrapper to reduce repetition and make tuning easier.
Description
Adjustments run in 1 loop instead of 2. Ratelimiting for important events with discord log
Motivation
Using 1 loop instead of 2 is self explaining for performance purposes. Idk why i made Rate limiting, since i did it for a project a year ago, wouldnt be bad to get it implemented since it has a purpose.
Usage Example
for the rate limiting:
PR Checklist