-
Notifications
You must be signed in to change notification settings - Fork 0
Async Patterns
This is the single biggest mental hurdle for newcomers. Database queries in omp-MySQL are asynchronous — they don't give you the answer immediately. Once this clicks, everything else is easy.
When you call mysql_execute, it starts the query and returns right away — the
rows aren't ready yet. They arrive later, in a callback.
mysql_execute(g_DB, "SELECT money FROM accounts WHERE id = 5", "OnMoney");
// <-- the money is NOT available here yet!
forward OnMoney();
public OnMoney()
{
new money; mysql_rs_get_int_by(0, "money", money); // <-- it's available HERE
return 1;
}Think of it like ordering food: you place the order (mysql_execute) and keep doing
other things; when the kitchen is done it calls your number (OnMoney).
This is the #1 mistake. This does not work:
GetMoney(playerid) // WRONG - can't return an async result
{
new money;
mysql_execute(g_DB, "SELECT money ...", ...);
return money; // money is still empty here!
}There's no way to "wait here and return the row." Instead, do the work in the callback:
LoadMoney(playerid)
{
mysql_execute(g_DB, "SELECT money FROM accounts WHERE id = 5", "OnMoney", "d", playerid);
}
forward OnMoney(playerid);
public OnMoney(playerid)
{
new money; mysql_rs_get_int_by(0, "money", money);
GivePlayerMoney(playerid, money); // act on it here
return 1;
}"But I really want to wait!" — there's
mysql_execute_syncwhich does block and return a result. Use it only for one-time startup (creating tables). In a player event it freezes your whole server. See Performance.
The callback runs later, so it needs to know who/what it's for. Pass extra args:
// "d" = pass playerid through to the callback
mysql_execute(g_DB, "SELECT ...", "OnLoaded", "d", playerid);
// "ds" = pass an int and a string
mysql_execute(g_DB, "SELECT ...", "OnLoaded2", "ds", playerid, someText);Format letters: d/i int, f float, s string. The callback's parameters must
match: public OnLoaded(playerid), public OnLoaded2(playerid, const text[]).
Because results arrive later, the player might have disconnected in the meantime. Always re-check:
public OnLoaded(playerid)
{
if (!IsPlayerConnected(playerid)) return 1; // they left; bail out
// ... safe to use playerid ...
return 1;
}There's a built-in helper for this common case: mysql_execute_for behaves like
mysql_execute but treats the first integer callback arg as a playerid and skips the
callback automatically if that player has left:
mysql_execute_for(g_DB, "SELECT ...", "OnLoaded", "d", playerid); // auto-skips if goneA playerid is a slot number (0–49). If player 5 leaves and a new player 5 joins
before your callback fires, acting on playerid could touch the wrong person. Guard
against it:
- Use
mysql_execute_for(skips if the original player left), and/or - Re-verify identity (e.g. compare the loaded account name to the current player name) for anything sensitive.
This is also why the mysql-admin demo wipes session state on every connect and never trusts a slot's old login — see Security.
- Two queries on the same handle run in order (FIFO) — predictable.
- Two queries on different handles can finish in any order.
- Don't assume query B sees query A's result unless B runs after A's callback, or they're in the same transaction.
Example race: saving on disconnect and an autosave timer firing at the same moment —
make saves idempotent (a plain UPDATE ... WHERE id = ? is safe to run twice).
The result set is only "active" inside the callback for that query. Reading
mysql_rs_* anywhere else gives empty/garbage. Read what you need in the callback (copy
it into variables if you must keep it).
public OnLoaded(playerid)
{
new rows; mysql_rs_row_count(rows);
if (rows == 0) { /* no such account */ return 1; }
new money; mysql_rs_get_int_by(0, "money", money);
return 1;
}Reading row 0 when there are no rows yields 0/empty and hides bugs.
- Keep player state in Pawn variables during play.
- Load from the DB into those variables on login (in a callback).
- Save them back periodically + on disconnect (fire-and-forget UPDATE).
- Never try to "return" a DB value — branch into a callback instead.
- Always assume the player might be gone when the callback runs.
Get these five right and you'll never fight the async model again.
Next: Performance · Transactions · Cookbook
Understand
Use
- Installing MySQL
- Docker Compose
- Getting started
- Configuration
- SQL crash course
- Designing your tables
- Storing game data
- Dates & times
- First queries
- Async patterns
- Reading results
- Prepared statements
- Passwords & hashing
- Transactions
- Models (active-record)
- Tutorial: login system
- mysql-admin demo
Deeper
Reference