Jump to content

Tutoriale - Greseli frecvente si altele


keNN.
 Share

Recommended Posts

  • Administrator
De curand am inceput sa deschid cateva gm-uri RPG si de foarte multe ori anumite gm-uri ma fac sa sterg system32 din calculator prin simplu fapt ca comit greseli mari/mici si asta m-a determinat sa fac acest topic.
 
Sa incepem mai intai cu baza de date. Pe foarte multi i-am auzit ca baza lor a depasit 2MiB in marime , ei bine daca structura bazei de date ar fi fost mult mai buna asta nu s-ar fi intamplat nici macar cu 1000 conturi inserate
Ce vreau sa spun cu asta? Ei bine....
 
Daca vrei sa salvezi spre exemplu valori de 1 sau 2 si care nu vor fi niciodata sub zero si niciodata mai amri de 127/255 se poate folosi cu usurinta tinyint. Ce este tinyint? Este un int care salveaza valori intre -128 si 127 (sau 0-255 daca unsigned)
Ce il face asa special si il recomand? Pentru ca foloseste doar 1byte de memorie si nu 4bytes ca si int normal ( ce mie 1byte ce imi sunt 4bytes , nu? ) , ei bine aceasta diferenta de bytes conteaza prin simplu fapt ca se acumuleaza pentru fiecare rand inserat(cont, masina sau whatever) si se ajunge la 1.5Mib (depinde de cat de mare e tabelul users/players etc) foarte repede
Si nu, valoarea pe care o pui la lenght/values conteaza doar ca afisaj cand vizualizati prin phpmyadmin tabelul. (desigur asta se aplica doar in cazul valorilor de tip integer)
Deci despre valori de nuemre intregi , sfatul meu este sa folositi si restul tipurilor de date , nu doar INT (asta desigur depinde de ce valori vreti sa stocati ). Cu cat e mai mic tipul de data cu atat mai bine pentru baza de date ( https://dev.mysql.com/doc/refman/5.7/en/integer-types.html )
 
Bun, acum sa vorbim de VARCHAR , am vazut in multe baze de date nsite valori imense pentru lucruri care nu depaseau 24/32 etc caractere. Deci de ce sa sacrifici memoria bazei de date si a HDD/SSD-ului cand poti sa specifici exact marimea valori pe care vrei sa o stochezi in acel VARCHAR? Ca nu cred ca daca modifici un numar , iti cade mana.
 
Vreti sa stocati data si ora in mysql? Evitati VARCHAR si stringuri/gettime/getdate inutile. Folositi DATETIME sau TIMESTAMP . De ce? De ce nu, e o alternativa mult mai eficienta si are exact acelasi rezultat(daca nu chiar mai bun). Vreti sa extrageti DATETIME/TIMESTAMP din bazade date? Faceti-o ca pe orcare string , nu trebuie sa va murdariti mana si gm-u scriind gettime/getdate , variabile pentru ele , formatare etc. Se poate face foarte simplu folosind CURENT_TIMESTAMP la valaorea default a coloanei din baza de date care contine timpul . Si daca vreti sa updatati data si ora (last seen spre exemplu).
 
"UPDATE `users` SET `lastseen` = CURRENT_TIMESTAMP WHERE `ID` = '5'"
Si cat despre storage engine evitati pe cat posibil MyISAM sau altele , folositi InnoDB (pentru ca InnoDB e cel mai bun pentur sa-mp )
Si ca exemplu, va ofer baza de date saints v1.3 vs baza de date saints v1.3 facuta de mine folosind ce am spus mai sus (tabelul users 😞http://imgur.com/rE8Q8RC (1008 conturi vs 2008 conturi).
 
Toate cele de mai sus pe langa faptul ca reduc marimea bazei de  date , dar deasemenea fac scrierea/citirea din baza de date putin mai eficeinte.Desigur , ar mai fi de mentionat despre tabele separate pentru anumite sisteme (pentru a incarca tabela users etc) dar este mult de scris (forgein key, design de structura etc).
 
Cam atat am avut de spus depsre baza de date (ar mai fi cateva mici chestii , dar nu astea sunt indeajuns)
Incat priveste gm-ul , am cateva obiectii si sfaturi (pentru ca si eu la randu meu am facut aceste greseli , fie ele importante sau mai putin importante).
 
In primul rand, evitati pe cat posibil mysql_query . De ce? Pentru ca poate produce unsync la server foarte rapid (query-uri mari  sau dese). mysql_query va stopa rularea gm-ului pana ce executa query-ul.
Folositi mysql_tquery cat mai mult posibil , pentru ca va lasa serverul sa-si faca treaba cat timp el executa query-ul. Pentru a incarca datele din baza de date(in special la sistemu de logare ) folositi mysql_pquery. De ce? Pentru ca e capabil sa execue query-uri in paralel (in cazul in care mai multi playeri se logheaza in acelasi timp pe server se va dovedi eficient) 
 
In al doilea rand, am vazut foarte multe query-uri de genul "UPDATE `users` SET `Money` = '%d' WHERE `id` = '%d'" ,  nimic gresit nu? Merge foarte bine. Dar problema e cauza din care merge. In cazul de fata, se trimit 2 stringuri spre baza de date care vor fi stocate in baza de date ca integer. Nu stiu motivul pentru care MySQL permite asta , dar are un sistem de conversie string > integer. Diferenta dintre a trimite un string si un integer pentru a fi stocate intr-o coloana de tip integer e putin neglijabila , dar nu tot timpul. Pentru ca acea conversie poate dura doar cateva nanosecunde sau poate chiar cateva secunde (depinde de sarcina pe serverul de mysql) , asa ca de ce sa lasi serverul de mysql sa lucreze in plus cand poti evita aceasta greseala.
 
In al treilea rand , am vazut ceva in gm-u saints care m-a socat( probabil sa fie si in alte gm-uri):
for(new turf = 1; turf < sizeof(TurfInfo); turf++)
{
   TurfInfo[turf][zTime] = 0;
   format(str,sizeof(str),"UPDATE `turfs` SET `Time`='0' WHERE `ID`='%d'",turf);
   mysql_query(SQL,str);
}
Aceasta prostie pur si simplu parca mi-a facut un neuron sa explodeze . Sa zicem ca serverul are 100 turfs  = 100 query trimise cu mysql_query. Ce inseamna asta? La ora 23 lag garantat + posibile probleme de sync si pierderi de date
De ce sa trimiti un astfel de query cand poti face asta:
for(new turf = 1; turf < sizeof(TurfInfo); turf++)
{
   TurfInfo[turf][zTime] = 0;
}
mysql_tquery(SQL,"UPDATE Turfs SET Time = 0");
Ambele fac acelasi lucru, dar modul , viteza si eficienta in care o fac le deiferentiaza enorm. 
Deci de ce sa sacrifici serverele (mysql/sa-mp) cand poti pastra lucrurile simple si eficiente.
 
In al patrulea rand, folosirea GetPlayerName foarte excesiv. Ce vrea usa spun cu asta? Pentru ca peste se fosoeste GetPlayerName cand de fapt se poate folosi o singura data in tot gm-u. Ce vrea usa psun cu asta? Simplu.
Cream o variabila pentru numele jucatorului( cel fara taguri sau alte ccaturi) in PlayerInfo(sau cum o fi) si folosim la OnPlayerConnect:
GetPlayerName(playerid, PlayerInfo[playerid][pNormalName], MAX_PLAYER_NAME);
Si cand vrem sa ne folosim de numele jucatorului , pur si simplu apelam la aceasta variabila. Deci de ce sa scriem 100000000 de GetPlayerName in tot gm cand putem scrie unul singur si ne facem viata mai simpla?
 
In al cincelea rand, am vazut foarte multe query de genul :
UPDATE `users` SET `Money`='%d' WHERE `name`='%s'
Ce e gresit in asta? Ei bine, este mult mai rapid si eficient sa ne folosim de ID-ul din baza de date , mai ales ca este un index principal ,iar cautarea dupa ID este mult mai rapida decat cautarea dupa nume (string)
Singurele momente in care vrei sa cauti un player in baza de date dupa nume este la conectare (login) si in cazul in care vrei sa te asiguri ca un player exista in baza de date (refferal , ban etc)
 
In al saelea rand, am vazut pe multi folosind cea mai neeficienta metoda de anti sql injection (ce cu cautarea dupa anumite caractere) . Mi se pare cea mai mare prostie sa faci asta.
De ce? Pentru ca exista mysql_format care are specificatorul %e care semnifica un string caruia ii se face escape. Daca nu vrei sa folosesti %e pentru a trimite stringuri catre baza de date , se poate folosi deasmeana mysql_real_escape_string care este folosit in background-ul lui %e 
 
In al saptelea rand, textdaw-urile globale NU se fac asa:
for(new i = 0; i < MAX_PLAYERS; i++)
{
	Speedd[i] = TextDrawCreate(483.600067, 145.848937, " ");
	TextDrawFont(Speedd[i], 1);
	TextDrawLetterSize(Speedd[i], 0.270000, 1.000000);
	TextDrawColor(Speedd[i], -505290241);
	TextDrawSetOutline(Speedd[i], 1);
	TextDrawSetProportional(Speedd[i], 1);
	TextDrawSetShadow(Speedd[i], 1);

	.....
}
Asta inseamna 650 (50 sloturi) de textdraw-uri gobale deja epuizate cand limita este de 2048 ( 13000 de textdarw-uri pentru 1000 sloturi) . Ce naiba?? Cine a facut asta , a fost cel mai "destept" om.
Daca vreti sa faceti textdraw per player, folositi textdraw-uri per player. Cum?
Creati cate o variabila pentru fiecare player textdraw in PlayerInfo (PlayerText: text1, PlayerText: text2 etc) si puenti ceva asemanator la OnPlayerConnect:
PlayerInfo[playerid][Speedd] = CreatePlayerTextDraw(playerid,483.600067, 145.848937, " ");
PlayerTextDrawFont(playerid,PlayerInfo[playerid][Speedd], 1);
PlayerTextDrawLetterSize(playerid,PlayerInfo[playerid][Speedd], 0.270000, 1.000000);
PlayerTextDrawColor(playerid,PlayerInfo[playerid][Speedd], -505290241);
PlayerTextDrawSetOutline(playerid,PlayerInfo[playerid][Speedd], 1);
PlayerTextDrawSetProportional(playerid,PlayerInfo[playerid][Speedd], 1);
PlayerTextDrawSetShadow(playerid,PlayerInfo[playerid][Speedd], 1);
Pentru fiecare in parte , si va folisiti de variabila respectiva . ( Player textdraw-urile se sterg la deconectarea playerului singure)
Cat inseamna asta pentru limita de textdraw-uri? 0 pentru cele gloable, si posibil 10-15 per player. Nu e mai ok asa? De ce sa ai 650 , 1300 ....13000 de textdraw-uri cand poti sa ai 10-15 per player ( limita per player este de 256 , si nu are legatura cu textdarw-urile gloable)
 
In al optulea rand, loop-uri de playeri/masini. Multi inca mai scriu loop-uride genul:
for(new i; i < MAX_PLAYERS;i++)

Cea ce nu mai este atat de eficient si bun , avand in vedere ca exista foreach si GetPlayerPoolSize disponibile si mult mai eficiente (GetVehiclePoolSize in cazuk masinilor)

for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++)
for(new i = 1, j = GetVehiclePoolSize(); i <= j; i++)
foreach(new i : Player)
Ce fac GetPlayerPoolSize si GetVehiclePoolSize? Ei bine, returneaza toate sloturile ocupate( sa zicem ca max de sloturi e 50, si ai abia 3 playeri conectati , GetPlayerPoolSize() va intoarce 3 pentru ca sunt numa 3 playeri / 3 sloturi ocupate , acelasi principiu se aplica si la GetVehiclePoolSize())
 
 
In al noulea rand, nu vreti ca playerul care se conecteaza sa mosteneasca datele precedentului player care tocmai a iesit? Nu este nevoie sa scrieti PlayerInfo[playerid][VariabilaX] = 0 la OnPlayerDisconect sau Conenct pentru fiecare variabila in parte.
Ce mai eficienta si rapida metoda este aceasta:
static const empty_player[pInfo];
PlayerInfo[playerid] = empty_player;
Ce face asta? Atribuie 0 pentru fiecare variabila in parte (pentru cele existente in PlayerInfo) , deci de ce sa scrii N randuri cand poti sa scrii numa 2
 
In al zecelea rand, evitati pe cat posibil array-uri mari (stringuri etc) . De ce sa definesti un string de 500 cells(2000 bytes) si sa folosesti pentru cel mult 60 caractere? E o pierdere de memorie inutila (da stiu nu e semnificativa , bla bla ,dar totu se aduna si pe termen lung nu are rost).
 
Cam atat, poate ar mai fi lucruri de spus dar pur si simplu nu-mi mai vin in minte. E cam lung ce-i drept dar am incercat sa explic cat de cat..... Sper sa nu ma injurati cand ajunget ila aceste randuri ca e prea mult de citit.
Sper sa fie cat de cat de folos cuiva si sper ca cel macar sfaturile legate de baza de date sa le vad aplicate (pentru ca pe asta se bazeaza RPG , multe query-uri si mult contact cu baza de date si nu vrem sa avem pierderi de date sau delay-uri sau mai stiu eu ce)
 
Banditul
Link to comment
Share on other sites

Good Job.

    _________________________________________________
    |                                                        _________             |
    |    |    /          /|        |\            |      /                   \          |
    |    |  /        /   |          |  \          |      |                              |
    |    |/       /     |           |    \        |      |                              |   
    |    |\              |           |      \      |      |        _____            |
    |    |  \            |           |        \    |      |                 \           |
    |    |    \          |           |          \  |      |                   |          |
    |    |      \        |           |            \|      \_________/           |
    |_______________________________________________|

Link to comment
Share on other sites

  • 4 weeks later...
  • 2 months later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...