Jump to content

Žebříček

Populární obsah

Showing content with the highest reputation on 03/12/18 in all areas

  1. PawnPlus 0.8 Stažení | Dokumentace Představuji vám svůj nový plugin, který z Pawnu udělá moderní programovací jazyk, v němž bude radost programovat pro SA-MP. Přidáním nových konstruktů a "typů" rozšiřuje sílu jazyka, ale zachovává a dokonce i navyšuje jednoduchost programování. Shrnutí hlavních funkcí Řetězce Všichni to známe – řetězec je pole znaků s pevnou maximální délkou, kterou vždy musíme znát, pokud chceme s řetězci cokoliv provést. Odteď už ne! PawnPlus přináší dynamickou alokaci řetězců, které lze spojovat, rozdělovat, porovnávat či jinak upravovat. Možná si říkáte, k čemu je to dobré, když funkce SA-MP (např. SendClientMessage) vyžadují předání řetězce jako pole, ale počkejte – jde to! Tento plugin umožní přetvořit jakoukoliv funkci SA-MP (i jiných pluginů) na variantu, která místo polí bere nové řetězce a funguje naprosto stejně. Asynchronní programování Nebojte, žádná vlákna či race-condition, ale lehké asynchronní programování pomocí úloh, jehož vzorem je jazyk C#. Pomocí nového příkazu jazyka lze pozastavit průběh stávající funkce a odložit jeho vykonávání na jakoukoliv jinou dobu a mezitím spustit třeba něco jiného. Již žádné složité používání SetTimerEx, s tímto pluginem lze prostě ve funkci čekat, než nastane žádaná situace, a pak pokračovat. "Reflexe" Pawn sice není natolik pokročilý, aby umožňoval jakoukoliv reflexi, ale v tomto pluginu naleznete i pár funkcí, které umožní např. zavolat nativní funkci pomocí jejího jména jako řetězce, či navázání libovolné funkce na konkrétní událost serveru. Příklady Vytvoření, spojení a předání řetězců #include "PawnPlus.inc" //Původní SendClientMessageToAll(color, const message[]) upravíme tak, že všechny "const x[]" nahradíme "AmxString:x". native SendClientMessageToAllStr(color, AmxString:message) = SendClientMessageToAll; //Pomocná funkce vytvářející řetězec (jehož maximální velikost známe). stock String:GetPlayerNameStr(playerid) { new name[MAX_PLAYER_NAME]; GetPlayerName(playerid, name, sizeof(name)); return str_new(name); } public OnPlayerConnect(playerid) { //Řetězce jsou reprezentované proměnnými se značkou "String". Lze je spojovat pomocí operátoru +. Funkce str_val umí převést libovolnou hodnotu na řetězec. new String:name = GetPlayerNameStr(playerid); new String:msg = str_new("Hráč ")+name+str_new(" (")+str_val(playerid)+str_new(") se připojil na server."); //Provede se zavolání funkce SendClientMessageToAll, jen místo druhého parametru se vezme dynamický řetězec namísto pole znaků. SendClientMessageToAllStr(-1, msg); } Rozdělení a porovnání řetězce //Obdobným způsobem upravena funkce SendClientMessage. native SendClientMessageStr(playerid, color, AmxString:message) = SendClientMessage; public OnPlayerCommandText(playerid, cmdtext[]) { new String:cmd = str_new(cmdtext); new String:name = cmd; new String:args = STRING_NULL; //Cyklus nalezne v řetězci mezeru a rozdělí jej na jméno a argumenty. new len = str_len(cmd); for(new i = 0; i < len; i++) { if(str_getc(cmd, i) == ' ') { name = str_sub(cmd, 0, i); args = str_sub(cmd, i+1); break; } } //Porovnání jména příkazu. if(name == str_new("/test")) { SendClientMessageStr(playerid, -1, args); //Vypsání argumentů. return true; } return false; } str_find a str_split bude v pozdější verzi. Pozastavení vykonávání funkce stock Odpocet() { SendClientMessageToAll(-1, "3"); wait_ms(1000); //Neblokující čekání (žádný cyklus). SendClientMessageToAll(-1, "2"); wait_ms(1000); SendClientMessageToAll(-1, "1"); wait_ms(1000); SendClientMessageToAll(-1, "0"); } //Poznámka: Čekání zablokuje vykonávání kódu až do nejbližší (externě vyvolané) funkce public. Pokud chcete, aby vnější kód pokračoval rovnou, použije CallLocalFunction. Čekání na libovolnou událost //Předělání funkce MoveObject tak, aby vracela úlohu, na niž lze "čekat" (tedy dokončení hýbání s objektem). stock task:MoveObjectTask(objectid, Float:X, Float:Y, Float:Z, Float:Speed, Float:RotX = -1000.0, Float:RotY = -1000.0, Float:RotZ = -1000.0) { //Úloha představuje abstraktní proces, který lze dokončit a jehož výsledkem může být nějaká hodnota. //task_new vytvoří novou prázdnou (nedokončenou) úlohu. new task:t = task_new(); //Zaregistruje novou "obsluhu" pro callback OnObjectMoved. Veřejná funkce SingleFireObjectTask se zavolá pokaždé, //kdy by se měla zavolat funkce OnObjectMoved a navíc se jí předá několik argumentů navíc. //specifikátor "e" značí předání ID nově vytvořeného objektu (to funkce pawn_register_callback i vrací). pawn_register_callback(#OnObjectMoved, #SingleFireObjectTask, "edd", t, objectid); MoveObject(objectid, X, Y, Z, Speed, RotX, RotY, RotZ); return t; } stock ObjectTest() { new obj = CreateObject(19300, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); print("Object created!"); //Proběhne čekání na úlohu; zbytek kódu se vykoná, až skončí. await MoveObjectTask(obj, 0.0, 0.0, 10.0, 5.0); print("Object moved!"); } //Nová obsluha události OnObjectMoved má navíc tři parametry (na začátku), jejichž hodnotu lze určit //v pawn_register_callback. Zbytek parametrů je z OnObjectMoved. forward SingleFireObjectTask(callback:id, task:task, obj, objectid); public SingleFireObjectTask(callback:id, task:task, obj, objectid) { if(obj == objectid) { //Pokud se pohnul objekt, na nějž je zaregistrovaná tato obsluha (uloženo v parametru obj), obsluha se odregistruje pawn_unregister_callback(id); //a úloha, která k ní náleží, je nastavena na hotovou (s nějakým výsledkem). task_set_result(task, objectid); } } Technické informace Jak to celé funguje? Technologie, na které celý tento plugin staví, se nazývá hookování. Hookování principiálně umožňuje přepsat kód nějaké funkce vlastním kódem, který se spustí místo něj. Díky patří Zeexovi za jeho knihovnu subhook. Tak například každá nativní funkce, která bere řetězce, používá funkci amx_GetAddr z API AMX. Tato funkce očekává adresu nějaké proměnné uvnitř stroje AMX a vrací ukazatel, který na ni ukazuje (to lze zapsat jako DAT+addr, kde DAT je ukazatel na počátek datové sekce stroje AMX, jenž lze získat z takto pojmenovaného registru). Tato funkce se musí zavolat vždy, když v Pawnu předáváme nějakou adresu (výstupní proměnné či pole) a na hookování je tedy ideální. Pokud AMX zjistí, že addr neukazuje dovnitř datové sekce, vrátí chybu, a pak přichází na scénu nový kód pluginu, který místo vrácení chyby interpretuje adresu jako (nějaký) identifikátor dynamicky alokovaného řetězce a ukazatel na jeho znaková data úspěšně vrátí. Tak se navenek každé funkci jeví předaný argument jako správný, ale když dojde na získání ukazatele, namísto dovnitř AMX ukazuje vrácené číslo na data pluginu. Zbývá maličkost, tedy zařídit, že addr bude pro dynamické řetězce ukazovat vždy do paměti, která je mimo AMX (sice to téměř jisté, ale nikoliv zaručené). Zde se uplatňuje vlastní tag AmxString, za nímž stojí funkce str_addr. Ta z ukazatele na řetězec spočítá relativní ofset vůči datové sekci stroje AMX, takže pro ukazatele mimo datovou sekci bude vytvořené číslo vždy chybné pro normální funkci amx_GetAddr. Funkce amx_Exec se zase volá vždy, když chce server spustit kód v AMX. AMX samotné podporuje předčasné ukončení vykonávání funkce pomocí vyvolání chyby a jedna taková chyba je i AMX_ERR_SLEEP (lze ji vykonat příkazem sleep v Pawnu), která značí, že se kód může obnovit. Jelikož hookuji každé volání amx_Exec, mohu to okamžitě zjistit a průběh vykonávání obnovit, aniž by o tom byl informován server. Zbývá vyřešit jediný problém – uložený obnovovací stav AMX je jen jeden a při jiném volání se zruší. Proto si plugin ukládá veškerou dynamickou paměť AMX (halda a zásobník) a registry k sobě, jakmile je výkon funkce pozastaven, aby nedošlo ke ztrátě. Při obnově je potom paměť AMX dočasně vrácena do původního stavu a spouštění původní funkce se obnoví. Funkce pawn_register_callback funguje pomocí hookování amx_Exec a amx_FindPublic, jež se zavolá vždy, když je třeba zjistit index veřejné funkce v AMX. Pokud je na název callbacku registrována nějaká obsluha, místo skutečného indexu se vrátí záporný index, který je následně identifikován v amx_Exec, která napřed vykoná příslušné obsluhy zjištěné z tohoto indexu. Řetězce a garbage collection Jak známo, každá dynamická alokace musí mít svoji dealokaci, jinak by začala unikat paměť, ale tady jsem si vesele používal str_new a žádnou dealokaci jsem neřešil, ačkoliv použitá paměť byla nová. Jak je to možné? Řetězce mají svůj vlastní garbage collector, který se stará o jejich mazání. Ve skutečnosti existují dva druhy řetězců – lokální (dočasné) a globální. Lokální řetězce jsou jejich nejčastější typ (String) a jejich existence je omezena po dobu výkonu nejvyššího callbacku. Je-li např. do skriptu vstoupeno callbackem OnPlayerConnected, všechny nové lokální řetězce existují po celý běh této veřejné funkce a jakmile ta skončí, smažou se. Pozor! – jakmile funkce skončí, použití jejích lokálních řetězců způsobí pád serveru. Řetězce se mažou jen po skončení nejvyšší veřejné funkce, takže pokud se ve skriptu skáče pomocí CallLocalFunction/CallRemoteFunction, vnitřní řetězce existují i po skončení vnitřních funkcí public. To ovšem neplatí pro SetTimer, jelikož vnitřní funkce se zavolá až po skončení vnější. Životnost lokálních řetězců není prodloužena ani pomocí asynchronních funkcí, neboť samotný callback je ukončen prvním čekáním. Proto existují globální řetězce, které nejsou automaticky mazány. Jejich použití je ale nebezpečné, neboť se musíte postarat o jejich smazání; musíte myslet na každé místo, kde by byl globální řetězec. Příklad: native print_s(AmxString:string) = print; new GlobalString:str; public OnFilterScriptInit() { str = str_new("Goodbye world!"); } public OnFilterScriptExit() { print_s(str); str_free(str); str = STRING_NULL; } Globální řetězce jsou označeny tagem GlobalString a převod na ně je automatický, pokud do takto značené proměnné přiřazujete. Lze toho docílit i voláním funkce str_to_global. Všechny funkce fungují stejně pro globální i lokální řetězce. Místo str_free můžete použít i funkci str_to_local, jež naopak vrátí řetězec do lokálního prostoru. To doporučuji použít v kombinaci s asynchronními funkcemi: public OnFilterScriptInit() { new String:str = str_new("Hello world!"); str_to_global(str); wait_ms(1000); str_to_local(str); print_s(str); } Mutabilita řetězců Řetězce jsou mutabilní, tedy měnitelné (až na jednu výjimku – STRING_NULL). To znamená, že některé funkce (například str_del) pouze upravují existující řetězec, takže pokud s ním pracují jiné funkce, dostanou jiná data. To lze vyřešit vytvořením nového řetězce (např. přes str_clone) nebo použitím funkcí, které samy o sobě tvoří nový řetězec (např. str_sub). STRING_NULL má speciální chování. Lze jej použít na místě normálního řetězce a všechna volání proběhnou úspěšně, ale jeho délka je vždy nulová. Funkce jako str_resize nebo str_set na něj tedy nemají žádný vliv. str_clone a jiné vytvářející funkce ale vždy udělá řetězec nový (a normální). Návratová hodnota asynchronní funkce Funkce obsahující čekání skončí vlastně předčasně a v takovém případě je nutno vrátit kontrolu serveru. Jelikož ale server očekává od funkce nějakou hodnotu hned, na případný příkaz return nemusí vůbec dojít. Od toho je tu funkce yield, jež navrátí hodnotu volajícímu kódu okamžitě: public OnFilterScriptInit() { new ret = CallLocalFunction(#Func, ""); printf("%d", ret); } forward Func(); public Func() { yield 12; wait_ms(1000); return 13; } Program vypíše 12 hned po spuštění. Návratovou hodnotu zbytku funkce (či z dalších volání yield) zatím nelze nijak odchytit, ale očekávám přidání něčeho na způsob coroutines v Lua (task_create z veřejné funkce a await, který lze zavolat vícekrát na takovou úlohu). Něco divného na mém kódu? await a yield jsou funkce, ale používám je jako příkazy. Pawn umožňuje vypustit závorky z takového volání, což vypadá rozhodně lépe. Také používám výraz typu #Func, což známe z maker, a je to totéž jako prosté "Func". Pokud řetězec je zároveň jméno funkce, líbí se mi toto použití pro odlišení.
    1 point
  2. Myslis nieco jako, ze by sa clovek musel pripojit o 8 rano a ist do Cluckin' Bell otacat burgre za $12/hod.? A ak by neprisiel, tak by ho vyhodili. Musel by si platit zdravotne, hypoteku na dom a uver na auto? Ludia hraju hry preto, aby zdrhli pred realitou
    0 points
×
×
  • Create New...