feat(scripting): add native callback system for request/response patterns#3871
feat(scripting): add native callback system for request/response patterns#3871frostfire575 wants to merge 3 commits intocitizenfx:masterfrom
Conversation
…erns Adds built-in callback functions to Lua and JS runtimes, eliminating the need for third-party libraries (ox_lib, QB-Core) for request/response patterns across client/server boundaries. Supports all four directions: - Client→Server: ServerCallback / ServerCallbackAwait - Server→Client: ClientCallback / ClientCallbackAwait - Same-side cross-resource: LocalCallback / LocalCallbackAwait Features: - Auto-namespacing by resource name to prevent conflicts - Optional restriction to same-resource only - Optional delay/throttle per callback - Configurable timeout via 'cb_timeout' ConVar (default 10s) - Player existence validation for server→client calls - Full Lua↔JS interoperability
|
If the goal is to standardize RPC natively, a cleaner alternative might be extending the existing Honestly, as someone building on this ecosystem, this implementation presents a few core issues:
|
appreciate the feedback, but genuinely curious what do you think is the better move here? drop the whole idea or because the way i see it:
so the plan is: drop local callbacks, namespace under citizen.* or keep it as it is, and add c# support. what you think? |
Fair point on the namespace stuff. I haven't messed with those frameworks in a minute, so I was probably a bit off on how they expose their callbacks nowadays The problem you're trying to solve (standalone scripts needing native RPC) is 100% valid, but the approach needs a pivot rather than just tweaking the current implementation. If Cfx.re is gonna take this on, a true engine-level integration makes way more sense than maintaining a built-in wrapper. Alternatively, maye just ship it as an official default resource (like Also, it'd be cool to get some more eyes on this to see what the rest of the community thinks |
|
Removed local callbacks . Wip -> Doing c# callback support..... |
Goal of this PR
Every FiveM framework out there (ox_lib, QB-Core, ESX) ships its own callback system because the platform doesn't have one. The pattern is always the same — pair two events with a request ID and a promise to get a response back from the server. This means every script that needs to fetch data from the server depends on a third-party library just to do something as basic as "ask the server and get an answer."
Right now, if you want to request data from the server and get a response back, you either pull in ox_lib, QB-Core, or roll your own wrapper around paired events. That's a lot of overhead for something as fundamental as "ask the server a question."
TriggerServerEventexists natively for fire-and-forget — this PR gives the platform its missing counterpart for request/response.This PR adds native callback functions directly into the Lua and JS scripting runtimes, so developers can do request/response communication the same way they use
TriggerServerEventtoday — without installing anything extra.How is this PR achieving the goal
The implementation lives entirely in the scripting layer (
scheduler.luafor Lua,main.jsfor JS) — no C++ changes needed. It uses the existing event system, promise/deferred infrastructure, and coroutine threading that's already built into the runtimes.Under the hood, two internal events handle the protocol:
__cfx_cb:req/__cfx_cb:respfor network callbacks (client↔server)Each callback name is automatically namespaced by resource (e.g.
RegisterServerCallback('getPrice', ...)in resourceshopbecomesshop:getPriceinternally). This prevents name collisions between resources without requiring developers to manually prefix everything.The API provides two calling styles:
ServerCallbackAwait) — yields the current coroutine until the response comes back, similar to howCitizen.AwaitworksServerCallback) — fires the request and handles the response in a separate coroutine via a callback functionRegistration supports an optional options table:
{ restricted = true }— only the same resource can call this callback{ delay = 2000 }— throttle calls to prevent spam (minimum ms between invocations)Why this is safe:
DoesPlayerExist)cb_timeoutConVar (default 10 seconds) — no dangling promisesxpcall/try-catchand propagated back to the caller cleanlyUsage / API Reference
Client → Server (Lua)
server.lua — Register a callback on the server:
client.lua — Call from the client:
Server → Client (Lua)
client.lua — Register a callback on the client:
server.lua — Call from the server:
Multiple Return Values (Lua)
JavaScript (client.js / server.js)
API Summary
RegisterServerCallback(name, handler, opts?)ServerCallbackAwait(name, ...)ServerCallback(name, cb, ...)RegisterClientCallback(name, handler, opts?)ClientCallbackAwait(playerId, name, ...)ClientCallback(playerId, name, cb, ...)Options table (optional, passed as 3rd argument to Register functions):
restrictedbooleanfalsedelaynumberfalseConVar:
cb_timeout— Timeout in ms before a pending callback is rejected (default:10000)This PR applies to the following area(s)
FiveM, ScRT: Lua, ScRT: JS
Successfully tested on
Game builds: 3258
Platforms: Windows
What was tested and results:
Every direction was tested across two separate resources (cb-test-a, cb-test-b) to verify both same-resource and cross-resource behavior, plus a dedicated JS resource for runtime interop:
resourceName:callbackNamesyntax — workingcb_timeoutms) — workingPerformance benchmark (local server, single player):
The ~50ms sequential latency is the inherent round-trip through FiveM's event system, not overhead from the callback layer. Payload size has negligible impact. Concurrent non-await calls show the system handles high throughput without issues.
Checklist
Fixes issues
No existing feature request issues found — this addresses a long-standing community need where every major framework (ox_lib, QB-Core, ESX) independently reimplements the same callback pattern because the platform lacks one natively.