Jump to content
Prosíme všetkých užívateľov, ktorý sa chcú opätovne pripojiť na discord aby znovu spárovali svoje účty kliknutím na "Discord" v navigácií a pripojili sa na server Read more... ×

IllidanS4

Uživatel
  • Příspěvků

    68
  • Registrován

  • Aktivní

  • Vítězných dnů

    16

IllidanS4 last won the day on Srpen 29

IllidanS4 had the most liked content!

Reputace

61 Jethro

About IllidanS4

  • Moto
    Nováček

Portfolio

  • Github
    IllidanS4
  • Pastebin
    IllidanS4

Kontaktní údaje

  • Skype
    illidans4

Návštěvníci

The recent visitors block is disabled and is not being shown to other users.

  1. IllidanS4

    plugin Yet Another Lua Plugin

    YALP Stažení | Dokumentace Nestačí-li Pawn vašim potřebám či chcete místo něj (nebo společně s ním) používat váš oblíbený dynamický jazyk Lua, nezoufejte! Poučiv se z nezdarů předchozích pluginů podobného ražení, stvořil jsem YALP. Na rozdíl od minulých podobných projektů YALP sám o sobě nezná žádné nativní funkce ani callbacky serveru, ale umožňuje s nimi prostřednictvím několika mocných nástrojů interagovat. Díky tomu není potřeba přidávat explicitně všechny funkce nabízené SA-MP; stačí je pouze zavolat. Tím pádem je plugin automaticky kompatibilní se serverem i všemi pluginy a všemi jejich minulými i budoucími verzemi (pokud se nezmění AMX API) a není potřebova ho udržovat aktuální z důvodů nové verze serveru (protože ani nepoužívá žádné pevné adresy v paměti). Co všechno plugin nabízí? Vše, co umí Pawn ve spojení se serverem. Jako to dokáže Pawn, tak i Lua umí přes YALP zavolat libovolnou nativní funkci, kterou server poskytne, a pomocí veřejných funkcí může server volat kód v Lue. Kompatibilita je zaručena i s pluginy, které využívají více funkcí Pawnu, například tagy či zpožděné vyhodnocení kódu (PawnPlus). Veškeré nové funkce přirozeným způsobem rozšiřují to, co dokáže samotná Lua, a zapadají do jejího ekosystému. Co všechno plugin nenabízí? Součástní kódu pluginu není žádná definice nativní funkce či callbacku z SA-MP; vše dokáže již samotná Lua pomocí ostatních funkcí nabízených pluginem. Sice to může připadat jako nevýhoda, že si všechno musíte nadefinovat sami, ale díky tomu se může plugin zaměřit na ty podstatné věci a neřešit například, co má vracet funkce GetPlayerPos (která místo toho bude vracet to, co po ní sami budete chtít). Jak to může fungovat? Hlavní část nových funkcí v Lue, balíček interop, při registraci načte z paměti speciálně vytvořený filterscript, který sám o sobě žádný kód neobsahuje, ale všechna volání funkcí v AMX API jsou pomocí hookování přesměrována do pluginu, který otestuje, zdali náhodou neodpovídají onomu filterscriptu. V podstatě tedy (z pohledu objektového programování) přepíše AMX API vlastní implementací jeho funkcí (jako amx_Register, amx_FindPublic, amx_Exec apod.). Tyto funkce se chovají naprosto stejně jako standardní AMX API, akorát uvnitř pracují s prostředím Lua. Navenek tedy nativní funkce nemají žádné ponětí o tom, že jsou volány z jiného jazyka, a mohou to zjistit pouze tak, že by přistupovali ke struktuře AMX přímo (což slušné funkce nedělají). Při volání nativních funkcí je využíván standardní zásobník AMX, takže i funkce pracující v běžné míře s pamětí také fungují. Příklad local interop = require "interop" local native = interop.native local commands = {} function commands.lua(playerid, params) -- není potřeba importovat funkci, stačí pouze zavolat! native.SendClientMessage(playerid, -1, "Hello from Lua!") return true end function commands.setpos(playerid, params) -- lze použít i funkce z jakéhokoliv pluginu local fail, x, y, z = interop.vacall(native.sscanf, interop.asboolean, params, "fff")(0.0, 0.0, 0.0) -- návratová hodnota se dá lehce specifikovat if fail then return native.SendClientMessage(playerid, -1, "Wrong arguments!") end native.SetPlayerPos(playerid, x, y, z) return true end function interop.public.OnPlayerCommandText(playerid, cmdtext) -- je potřeba správně převést hodnoty (YALP nedokáže určit jejich typ) playerid = interop.asinteger(playerid) cmdtext = interop.asstring(cmdtext) -- můžete využít všech výhod, které Lua nabízí local ret cmdtext:gsub("^/([^ ]+) ?(.*)$", function(cmd, params) local handler = commands[string.lower(cmd)] if handler then ret = handler(playerid, params) end end) return ret end Krom balíčku interop nabízí YALP i další, například timer. Ve spojení s coroutines lze psát plně asynchronní kód: local timer = require "timer" local function sleep(ms) coroutine.yield(timer.ms, ms) end -- async je funkce poskytnutá pluginem, která vytvoří coroutine a každé volání yield zavolá poskytnutou funkci (zde timer.ms) a předá coroutine.resume async(function() print("a") sleep(100) print("b") sleep(100) print("c") sleep(100) print("d") end) Skript lze propojit i s pluginy, které používají zpožděné vykonání kódu. Zde příklad interakce s PawnPlus: local interop = require "interop" local wait_ms = interop.native.wait_ms interop.forward(async, function() print("a") wait_ms(100) print("b") wait_ms(100) print("c") wait_ms(100) print("d") end) interop.forward obsahuje volání amx_Exec, aby mohl PawnPlus toto volání zachytit a zpracovat chybový kód (AMX_ERR_SLEEP). Pokud nativní funkce tuto chybu vrátí a YALP může zavolat yield, učiní tak a vrátí funkci interop.sleep a návratový kód z nativní funkce. interop.sleep je zavolán implementací funkce async a uloží předané argumenty (návratový kód a navazující funkci) do chyby, kterou vyhodí. Chyba je zachycena uvnitř implementace funkce amx_Exec v pluginu a návratové funkci je přiřazeno číslo, které je uloženo do struktury AMX. PawnPlus tuto strukturu uloží a po specifikované době obnoví a zavolá amx_Exec s parametrem AMX_EXEC_CONT. To zjistí YALP a místo hledání funkce v tabulce veřejných funkcí vezme správnou funkci podle jejího uloženého čísla. Celý tento mechanismus se dá spojit, tedy lze používat výše uvedené sleep i nativní funkce. Jen je potřeba zajistit, aby navazovací funkce byla zavolána uvnitř interop.forward (aby její vrácení mohly zpracovat jiné pluginy): local function sleep(ms) coroutine.yield(function(cont, ...)return timer.ms(function(...)return interop.forward(cont, ...)end, ...)end, ms) end A aby nedošlo ke křivdě, zde je i příklad jednoho možného volání GetPlayerPos: local x, y, z = interop.vacall(interop.native.GetPlayerPos, interop.asnone, playerid)(0.0, 0.0, 0.0) interop.vacall je primárně určena k volání variadických funkcí (protože jejich argumenty jsou předávány referencí a je pro ně potřeba alokovat místo), ale díky jejímu fungování se dá použít i pro funkce, které mají jako parametry reference. interop.vacall napřed vrátí jinou funkci, jíž se teprve mají předat referencové argumenty (playerid se jako reference nepředává) a ta zavolá interop.native.GetPlayerPos(interop.asnone, playerid, a, b, c), kde a, b, c jsou adresy dočasných proměnných alokovaných na haldě AMX. Mezifunkce si pamatuje typy variadických argumentů a po skončení nativní funkce kromě uvolnění proměnných z haldy je také převede na jejich původní typy (ty jsou určené podle 0.0, 0.0, 0.0). Jistě jste si všimli hodnot interop.asnone, interop.asboolean či interop.asinteger. To jsou všechno naprosto normální funkce, ale YALP pozná, pokud je použijete jako první argument nativní funkce, že určují převod návratové hodnoty (protože funkci do nativní funkce předat nelze). Pokud chcete, můžete si klidně vytvořit vlastní takovou funkci, která může vracet i více než jednu hodnotu!
  2. IllidanS4

    plugin PawnPlus 0.8

    Sice jsem sem dlouhou dobu nepsal, ale na pluginu se stále pracuje; momentálně je na verzi 0.8. Pro plný seznam změn doporučuji projít verze na GitHubu, případně aktuální wiki. Zde je pár novinek za poslední měsíce: Rozšířen systém tagů o runtime podporu (hledání podle názvu, dědičnost, dynamické volání či definování vlastních operací apod.). Všechny kontejnery (seznamy, mapy, variant) obsahují ke každé hodnotě i její tag a mohou náležitě volat jeho vlastní operace (např. destruktor). Pomocí těchto operací můžete např. vytvořit komplexní objekt a zařídit, že všechny jeho podobjekty (mapy, seznamy, ale třeba i soubory) budou vždy smazány. Maximálně zvýšená bezpečnost kódu. Neplatné ukazatele jsou kontrolovány a zachyceny ve všech situacích. Bylo opraveno množství chyb způsobujících pády či záseky. "Strážci" (guards). Strážce můžete vytvořit ke každému objektu a on zařídí jeho automatické smazání (na konci bloku či na konci vstupu do skriptu). Pokud je kód přerušen asynchronní operací, strážce objekt nesmaže a počká na skutečné ukončení kódu. Dynamické hookování funkcí. Kromě callbacků lze i na jakoukoliv nativní funkci navázat vlastní kód, který se vyvolá i pokud je funkce zavolána z jiných skriptů (nebo dokonce i z pluginů). Zatím lze pouze přidat prehook/posthook (filtr), ale očekávám plné hookování v 0.9. Systém úloh (tasks) rozšířen o nové možnosti a operace. Zejména funkce task_any a task_all se dají využít pro spojení jiných úloh (první hotova / všechny hotovy). Úlohy nyní mají i svůj garbage collector, který všechny hotové úlohy co nejdříve smaže (tomu lze předejít resetováním úlohy či nastavením, že nemá být smazána). Každá úloha také namísto výsledku může obsahovat chybový stav, který lze použít pro signalizaci výjimečných situací. Uživatelský kód může čekat na skončení úlohy a stav zachytit a zpracovat. V 0.9 bude tento systém dále vylepšen. Přidáno množství low-level funkcí pro manipulaci s AMX. Nejpokročilejší je asi "forking", tedy možnost lokálně zduplikovat AMX a vykonat v něm "chráněný kód". Takový kód nemůže nijak zasahovat do původní paměti skriptu a lze z něj zachytit i návratové hodnoty či chyby. Navíc, jelikož se jedná o zcela separátní AMX, v něm lze spustit nové vlákno, které nebude nijak zasahovat do původního skriptu a poběží nezávisle. Tím pádem lze nyní jednoduše vytvořit nové (dočasné) vlákno, aniž by byl blokovaný skript. Všechny dynamické kontejnery mohou obsahovat 2D a 3D pole a indexovat je. Podpora je i pro pole nepevných rozměrů. Seznamy a mapy mají pokročilé iterátorové objekty, které umožňují upravovat, mazat či přidávat prvky do kolekcí. Iterátory jsou naprosto bezpečné, neboť si uchovávají odkaz na původní kolekci a sledují její změny i odstranění. Nestane se, aby kvůli smazání kolekce došlo k chybnému čtení paměti. Iterátory podporují i generické použití. Ve verzi 0.9 nejspíše přibudou i další iterovatelné kolekce (spojové seznamy, fronty atd.). Iterátory nejsou rozlišeny typem původní kolekce, všechny operace fungují na libovolný iterátor (pokud jsou kompatibilní s původní kolekcí).
  3. IllidanS4

    pomoc Ukazatelé

    Máš televizor, ale jsi zapomnětlivý a nikdy si nepamatuješ, kde se ten televizor nachází. Televizor je příliš obtížné brát stále s sebou, takže si na kousek papíru napíšeš "obývák", nakreslíš k tomu obrázek televizoru a papír nosíš stále u sebe. Díky němu se stačí vždy podívat a hned se k televizoru můžeš dostat. Televizor je objekt v paměti a obývák je jeho adresa v paměti. Paměť je sice lineární, ale procházet ji celou nejde, pokud bys v ní chtěl hledat konkrétní objekt, takže musíš znát jeho adresu (celočíselný ofset od začátku paměti). Papírek/ukazatel je pak datový typ, který tuto adresu obsahuje, jehož prostřednictvím se k původnímu objektu můžeš dostat. & je zpravidla operátor získání adresy daného objektu (proměnná -> ukazatel), zatímco * získání proměnné, ve které ten objekt je (ukazatel -> proměnná).
  4. IllidanS4

    plugin PawnPlus 0.8

    Verze 0.4 vydána, přidávající heterogenní seznamy a mapy (umožňují indexovat i pomocí polí a řetězců). Více informací zde.
  5. IllidanS4

    ostatní 0.3.8 zrušeno?

    Jak to vypadá s českou komunitou a 0.3.DL? Pár serverů vidím s 0.3.DL, pár ještě s 0.3.8, ale jak jsou na tom statistiky? Vyplatí se přecházet?
  6. IllidanS4

    plugin PawnPlus 0.8

    Verze 0.3 vydána. Mimo jiné byla přidána podpora pro paralelní spouštění kódu: public OnFilterScriptInit() { print("A"); threaded(sync_explicit) { for(new i = 0; i < 100000; i++) printf("%d", i); } print("B"); } V bloku threaded se kód vykonává paralelně s hlavním vláknem serveru, což znamená, že server může vesele pokračovat ve své práci, aniž by se čekalo na skončení kódu. Parametr (sync_*) ovlivňuje, jakým způsobem je provedena synchronizace komunikace kódu se serverem: sync_explicit vykoná kód bez přerušení, tedy volání všech nativních funkcí je provedeno ve vlákně, a callbacky z vnějšku musí počkat, než kód skončí, nebo než je zavolána funkce thread_sync, která vyvolá jejich spuštění (něco jako yield v terminologii kooperativních vláken); sync_auto synchronizuje všechna volání nativních funkcí s hlavním vláknem a sync_interrupt při volání callbacku natvrdo vlákno pozastaví, ať je kdekoliv. Návrh AMX není kompatibilní s používáním vícero vláken, takže platí, že v každém programu smí nejvýše jedno vlákno v každém momentě vyvolávat kód. Pokud chcete spouštět nějakou dlouhou operaci, doporučuji ji umístit do filterscriptu.
  7. IllidanS4

    define using

    Patrně se vám již stalo, že jste otevřeli soubor, aniž byste ho poté zase zavřeli, či jste zapomněli smazat výsledek dotazu SQLite apod. Možná z vyšších programovacích jazyků znáte příkaz using či try-with-resources a litujete, že něco takového není i v Pawnu. To ale může být! #define using(new%9\32;%0:%1=%2) for(new %0@guard:guard@%1=(%0@guard:%2),%0:%1=(%0:guard@%1);_:%1!=cellmin;_:%1=cellmin) Stačí tento řádek, abyste hned mohli vesele začít používat using na libovolné výrazy, jakmile dodefinujete destruktor. Je pravděpodobné, že jste o destruktorech ještě neslyšeli, poněvadž v dokumentaci o nich není ani zmínka, ale principiálně se jedná o něco podobného, jako je automatické mazání proměnných v C++. Pokud má určitý tag definován destruktor, zavolá se tento v momentě, kdy je ukončen blok obsahující proměnnou s tímto tagem. Jednoduchý destruktor příkazu using pro tag File: vypadá takto: stock operator~(File@guard:arr[], count) { for(new i = 0; i < count; i++) { if(_:arr[i] != 0) fclose(File:arr[i]); } } Destruktor dostane jako parametr pole všech hodnot, které opustily blok, a jeho velikost. Tento destruktor se tedy zavolá vždy, pokud proměnná s tagem File@guard: (to je jen název) opustí blok. Makro using zařídí, že taková proměnná bude existovat vždy, když se takový blok otevře, a destruktor se zavolá při každém vyskočení z bloku. Např. příkaz using(new File:f = fopen("file", io_read)) se přetransformuje na toto: for(new File@guard:guard@f = File@guard:fopen("file", io_read), File:f = File:guard@f; _:f != cellmin; _:f = cellmin) Toto zneužívá sémantiku cyklu for k tomu, aby spustilo nějaký kód na začátku a na konci bloku. Napřed se vytvoří dvě proměnné - guard@f a f. První obsahuje vrácenou hodnotu, druhá její kopii. Hodnota cellmin je použita jako ukončovací; na konci bloku je nastavena a v dalším průchodu ukončí cyklus. Mohl bych použít klasický for s jednoprvkovým intervalem, ale tohle mi přijde elegantnější. Teď k té důležité části: jakmile řízení opustí cyklus, proměnná guard@f zmizí a tedy se spustí její destruktor, který zavře soubor. Zbývá otázka - k čemu destruktor? Pokud mohu vyvolat kód na konci bloku, proč smazání nedám tam? Proto: using(new File:f = fopen("f", io_read)) { return; } Bez destruktorů by se soubor nikdy nezavřel, protože řízení opustí funkci ještě před ukončením cyklu. Naopak destruktor je vyvolán i v případě, kdy se cyklus opouští násilně, tedy příkazem break, return nebo exit (ale ne goto a sleep). Díky tomuto se chová using tak, jak by měl.
  8. IllidanS4

    plugin PawnPlus 0.8

    Vyhledávání samotné je 6× rychlejší. Nejhorší případ se vyskytuje při řešení konkrétního problému, pro nějž hledáme adekvátní nástroj. Dynamické řetězce jsou dobré na jedny věci (zpravidla programování na vyšší úrovni), buffery a pole pevné délky na věci jiné. Koneckonců fakt, že i str_format má první argument format[] a ne String:format naznačuje, že mi nejde o kompletní nahrazení klasických řetězců. Edit: Zkusil jsem přístup přímo k bufferu řetězce, ale STOR.I má patrně kontrolu adresy: public OnFilterScriptInit() { new String:str = @("ABCDEFGH"); CallLocalFunction(#OnStringReceive, "d", _:str_buf_addr(str)); print_s(str); } forward OnStringReceive(str[]); public OnStringReceive(str[]) { printf("%s", str); //ABCDEFGH str[0] = 'X'; } Jiné instrukce ve starší verzi AMX kontrolu nemají, takže by to s tím emitem šlo, ale je otázka, jak rychlé by to bylo.
  9. IllidanS4

    plugin PawnPlus 0.8

    Špatně jsem se vyjádřil, ale je rozdíl v tom, zdali chceš naformátovat řetězec podle nějakého vzoru, či zdali chceš jen spojit dvě věci dohromady. Většina jazyků má podporu pro oboje. Uznávám, že momentálně nemám řetězce uložené nejlepším způsobem (protože jsem ještě nedodělal stable_vector), ale to argumentuješ proti všem jazykům, které mají dynamicky alokované řetězce. Předpokládám, že C++ alokuje nějak rozumně. new start, top = 1000000; new buf[] = "aaaaaaaaaaabbbbbbbbbbbbccccccccccccccc"; new String:str1 = str_new(buf); start = GetTickCount(); for(new i = 0; i < top; i++) { buf[i % 38] = buf[37 - i % 38]; } printf("%d", GetTickCount()-start); start = GetTickCount(); for(new i = 0; i < top; i++) { str_setc(str1, i % 38, str_getc(str1, 37 - i % 38)); } printf("%d", GetTickCount()-start); Horní 348,78 ms, dolní 520,11 ms, tedy zhruba 1,5× pomalejší, což na volání nativní funkce není špatné. AMX ti přímou adresaci paměti nedovolí, takže bez nativní funkce se to neobejde. Souhlasím, ale chtěj po většině pawnerů, aby přestali používat for(new i = 0; i < strlen(s); i++). Taky jsem párkrát využil chytré dělení řetězce tímto způsobem (při zalamování textu a rozdělování podle konců řádků) a je fakt, že když je dobrý algoritmus, nepotřebuje to žádnou alokaci navíc. Ale takovýto kód je nízkoúrovňový, zdlouhavý na vytvoření a netriviální pro ne příliš pokročilé kodéry. Abych měl taky příklad "z reálného světa", kde by se mi toto osvědčilo, použil jsem část svého módu, konkrétně tu, kde se vytvářejí vlastní příkazy (což mohou admini). Registruje se alias příkazu a reálný příkaz, který se místo něj vykoná. #define ALIAS_CMD_LENGTH 129 enum CMD_ALIAS_ARRAY { CMD_ALIAS_ARRAY_NEW[ALIAS_CMD_LENGTH], CMD_ALIAS_ARRAY_REAL[ALIAS_CMD_LENGTH] }; static commands_arr[60][CMD_ALIAS_ARRAY]; stock TryCustomCommandArray(cmdtext[]) { for(new i = 0; i < sizeof commands_arr; i++) { if(commands_arr[i][CMD_ALIAS_ARRAY_NEW][0]) { if(!strcmp(cmdtext, commands_arr[i][CMD_ALIAS_ARRAY_NEW])) { //print(commands_arr[i][CMD_ALIAS_ARRAY_REAL]); return true; } } } return false; } stock CreateCustomCommandArray(newcmd[], realcmd[]) { for(new i = 0; i < sizeof commands_arr; i++) { if(commands_arr[i][CMD_ALIAS_ARRAY_NEW][0] && !strcmp(commands_arr[i][CMD_ALIAS_ARRAY_NEW], newcmd)) { commands_arr[i][CMD_ALIAS_ARRAY_NEW][0] = '\0'; } if(!commands_arr[i][CMD_ALIAS_ARRAY_NEW][0]) { commands_arr[i][CMD_ALIAS_ARRAY_REAL] = '\0'; strcat(commands_arr[i][CMD_ALIAS_ARRAY_NEW], newcmd, ALIAS_CMD_LENGTH); strcat(commands_arr[i][CMD_ALIAS_ARRAY_REAL], realcmd, ALIAS_CMD_LENGTH); return true; } } return false; } stock RemoveCustomCommandArray(command[]) { for(new i = 0; i < sizeof commands_arr; i++) { if(commands_arr[i][CMD_ALIAS_ARRAY_NEW][0] && !strcmp(commands_arr[i][CMD_ALIAS_ARRAY_NEW], command)) { commands_arr[i][CMD_ALIAS_ARRAY_NEW][0] = '\0'; return true; } } return false; } enum CMD_ALIAS_STR { GlobalString:CMD_ALIAS_STR_NEW, GlobalString:CMD_ALIAS_STR_REAL }; static commands_str[60][CMD_ALIAS_STR]; stock TryCustomCommandStr(String:cmdtext) { for(new i = 0; i < sizeof commands_str; i++) { if(commands_str[i][CMD_ALIAS_STR_NEW] && commands_str[i][CMD_ALIAS_STR_NEW] == cmdtext) { //print_s(commands_str[i][CMD_ALIAS_STR_REAL]); return true; } } return false; } stock CreateCustomCommandStr(String:newcmd, String:realcmd) { for(new i = 0; i < sizeof commands_str; i++) { if(commands_str[i][CMD_ALIAS_STR_NEW] && commands_str[i][CMD_ALIAS_STR_NEW] == newcmd) { commands_str[i][CMD_ALIAS_STR_REAL] = realcmd; return true; } if(!commands_str[i][CMD_ALIAS_STR_NEW]) { commands_str[i][CMD_ALIAS_STR_NEW] = newcmd; commands_str[i][CMD_ALIAS_STR_REAL] = realcmd; return true; } } return false; } stock RemoveCustomCommandStr(String:command) { for(new i = 0; i < sizeof commands_str; i++) { if(commands_str[i][CMD_ALIAS_STR_NEW] && commands_str[i][CMD_ALIAS_STR_NEW] == command) { //str_free(commands_str[i][CMD_ALIAS_STR_NEW]); commands_str[i][CMD_ALIAS_STR_NEW] = STRING_NULL; return true; } } return false; } public OnFilterScriptInit() { new start, top = 10000; start = GetTickCount(); for(new i = 0; i < top; i++) { CreateCustomCommandArray("/acmd", "/real_command1"); CreateCustomCommandArray("/bcmd", "/real_command2"); CreateCustomCommandArray("/ccmd", "/real_command3"); CreateCustomCommandArray("/dcmd", "/real_command4"); TryCustomCommandArray("/acmd"); TryCustomCommandArray("/bcmd"); TryCustomCommandArray("/ccmd"); TryCustomCommandArray("/dcmd"); RemoveCustomCommandArray("/acmd"); RemoveCustomCommandArray("/bcmd"); RemoveCustomCommandArray("/ccmd"); RemoveCustomCommandArray("/dcmd"); } printf("%d", GetTickCount()-start); start = GetTickCount(); new String:acmd = @("/acmd"); new String:acmd2 = @("/acmd"); new String:bcmd = @("/bcmd"); new String:bcmd2 = @("/bcmd"); new String:ccmd = @("/ccmd"); new String:ccmd2 = @("/ccmd"); new String:dcmd = @("/dcmd"); new String:dcmd2 = @("/dcmd"); new String:real_command1 = @("/real_command1"); new String:real_command2 = @("/real_command2"); new String:real_command3 = @("/real_command3"); new String:real_command4 = @("/real_command4"); for(new i = 0; i < top; i++) { CreateCustomCommandStr(acmd, real_command1); CreateCustomCommandStr(bcmd, real_command2); CreateCustomCommandStr(ccmd, real_command3); CreateCustomCommandStr(dcmd, real_command4); TryCustomCommandStr(acmd2); TryCustomCommandStr(bcmd2); TryCustomCommandStr(ccmd2); TryCustomCommandStr(dcmd2); RemoveCustomCommandStr(acmd2); RemoveCustomCommandStr(bcmd2); RemoveCustomCommandStr(ccmd2); RemoveCustomCommandStr(dcmd2); } printf("%d", GetTickCount()-start); } Horní 812 ms, dolní 153 ms, tedy zhruba 5× rychlejší. Řetězce jsem si alokoval napřed, protože normální pole jsou taky alokované jenom jednou, a dvakrát jsou tam proto, aby to náhodou neporovnávalo podle reference. Možná bys mohl namítnout, že hašovací tabulka by to urychlila, ale i při jednom záznamu v poli je porovnávání v pluginu 10× rychlejší.
  10. IllidanS4

    plugin PawnPlus 0.8

    Taková funkce už existuje - str_format, jež je plánována na 0.2. Spojování řetězců (pomocí + a od 0.1.1 i pomocí %) je stále přítomno a ideální v případě, máme-li po ruce více dynamických řetězců. Výhodna je značná - možnost transportu (SetTimerEx) a práce s řetězci napříč skripty, odstranění nutnosti počítat při každé operaci s velikostí cílového bufferu, nezávislost na konkrétním skriptu a odstranění obecných nároků na alokaci v paměti skriptu. Už jen pole řetězců typu [MAX_PLAYERS][128] ti, pokud nepoužíváš PVars, přidá 50 KB velikosti samotného kompilátu. V kombinaci s jinými pluginy, které taky umožňují ukládat buňky (např. seznamy, EXTRA_DATA v streameru) je dynamická externí alokace jediná možnost, jak v takových strukturách ukládat liboovolné řetězce. Jediná alternativa je udělat si pole, kam budeš házet očíslované řetězce, ale pak už není moc velký rozdíl v tom, když použiješ tohle. Navíc dostaneš garbage collector a obecně rychlejší funkce na manipulaci. Absolutní rychlost je jedna věc, ale vezmi si, že všechny funkce standardní knihovny pro řetězce Pawnu musejí počítat velikost vstupu. Pak ti strlen běží lineárně, protože pokaždé hledá nulový znak, zatímco str_len má složitost konstantní. Syntaxe je taková, s jakou by ses setkal v každé knihovně pro lepší práci s řetězci. Pro str_new existuje alias @ - (str_new("x") == @("x")) a pro str_val se dá zapnout alias @@. Pawn bohužel objektový přístup nenabízí, takže místo (efektnějšího) zápisu str.delete(0, 2) musí být str_delete(str, 0, 2). Nevím, jak moc "jednodušší" by sis ji představoval. Koneckonců, namísto !strcmp(a, b ) můžeš používat a == b. To je vcelku velké zjednodušení, nemyslíš? Nevím, s kolika dalšími jazyky máš zkušenost, ale jen si vezmi, jak se to dělá v C++: std::string str1 = "Hello world"; size_t len = str1.size(); std::string half1 = str1.substr(0, len/2); std::string half2 = str1.substr(len/2); assert(half1+half2 == str1); V Pawnu s tímto pluginem (a v moderních jazycích jako je C#, Java a v podstatě i Lua) to je naprostá analogie: new String:str1 = @("Hello world"); new len = str_len(str1); new String:half1 = str_sub(str1, 0, len/2); new String:half2 = str_sub(str1, len/2); assert(half1+half2 == str1); A teď to srovnej s klasickým Pawnem: new str1[] = "Hello world"; new len = strlen(str1); //chytřeji sizeof(str1)-1 new half1[sizeof(str1)/2+1], half2[sizeof(str1)/2+1]; strmid(half1, str1, 0, len/2); strmid(half2, str1, len/2, len); new str2[sizeof(half1)+sizeof(half2)]; strcat(str2, half1); strcat(str2, half2); assert(!strcmp(str1, str2)); Takhle se to dělalo naposledy v C. A narozdíl od C je Pawn "bezpečnější", takže každá alokace v new ti zároveň tu paměť vynuluje. To je naprosto zbytečné, když tam hned následně zapisuji.
  11. IllidanS4

    plugin PawnPlus 0.8

    S využitím nové funkce str_format a podpory bufferů: native GetConsoleVarAsStringStr(const varname[], AmxStringBuffer:buffer, len) = GetConsoleVarAsString; public OnFilterScriptInit() { new start, top = 100000; start = GetTickCount(); new buf[128]; new val[32]; for(new i = 0; i < top; i++) { GetConsoleVarAsString("rcon_password", val, 32); format(buf, sizeof buf, "rcon_password=%s", val); } printf("%d", GetTickCount()-start); start = GetTickCount(); new String:str = str_new(""); new String:strbuf = str_new(""); for(new i = 0; i < top; i++) { str_resize(strbuf, 32); str_resize(strbuf, GetConsoleVarAsStringStr("rcon_password", strbuf, 32)); str_set_format(str, "rcon_password=%S", strbuf); } printf("%d", GetTickCount()-start); } Dolní kód běží 1,5× pomaleji, což zase není taková velká ztráta. Navíc pokud by chtěl někdo použít tuto funkci pro formátování řetězců do MySQL/SQLite (%q už jde), řekl bych, že 5 nanosekund na formátování nikomu vadit nebude, zvlášť když odpadá nutnost odhadovat velikost výstupního bufferu. To považuji za mnohem větší výhodu. Když jsme u toho MySQL, už jsem ověřil, že pomocí asynchronního programování se dají dělat asynchronní dotazy na MySQL bez nutnosti všelijakých veřejných funkcí a předávání dočasných parametrů: stock task:mysql_pquery_task(MySQL:handle, const query[]) { new task:t = task_new(); mysql_pquery(handle, query, #OnQueryTaskCompleted, "i", t); return t; } forward OnQueryTaskCompleted(task:task); public OnQueryTaskCompleted(task:task) { task_set_result(task, 0); } public OnMySQLConnected() { await mysql_pquery_task(db, "SELECT COUNT(*) AS `count` FROM `characters`"); new count; cache_get_value_name_int(0, "count", count); printf("%i", count); return 1; }
  12. IllidanS4

    plugin PawnPlus 0.8

    Má to jisté výhody, má to jisté nevýhody. Mojí počáteční prioritou byla jednoduchost používání; věci jako alokace a časté používání nativních funkcí mohou kód snadno zpomalit. Tvůj příklad dával dobu trvání v poměru 1 : 50 pro klasický kód, ale to bylo z důvodu dvou věcí, které jsem už zmínil. Jinak v nové verzi bude podpora pro buffery, což znamená, že ani funkce jako GetPlayerName nemusí ukládat výsledek do mezipole, nýbrž rovnou do dynamického řetězce. Při snížení počtu volání a alokací se kód využívající plugin přiblíží rychlostí ke klasickému, a pokud vytvořím lepší str_format, bude i rychlejší.
  13. IllidanS4

    plugin PawnPlus 0.8

    Tenhle kód momentálně nelze takto porovnat, protože format zatím nemá přímou alternativu. Tu ale plánuji přidat. Testoval jsem to s str_append (to je lepší, nevytváří tolik řetězců) a tam je sice PawnPlus mnohem pomalejší, ale je to taky kvůli mnohem častějším voláním nativních funkcí, a navíc ty kódy nejsou ekvivalentní, i kdyby stačilo jednou zavolat (hypotetické) str_format, neboť v prvním případě se řetězec "ATomas" nemusí vytvářet vůbec.
  14. IllidanS4

    plugin PawnPlus 0.8

    První řádek horní polovina kódu, druhý řádek spodní polovina. Poslední sloupec je průměr. Stačí říct a udělám měření pro jiný typ operace.
  15. IllidanS4

    plugin PawnPlus 0.8

    Beze všeho, zde máš jedno měření ekvivalentních operací pomocí polí a pomocí PawnPlus: public OnFilterScriptInit() { new start, top = 10000; start = GetTickCount(); new buf[128]; new val[16]; for(new i = 0; i < top; i++) { buf[0] = 0; for(new j = 0; j < 10; j++) { valstr(val, j); strcat(buf, val); } } printf("%d", GetTickCount()-start); start = GetTickCount(); new String:str = str_new(""); for(new i = 0; i < top; i++) { str_clear(str); for(new j = 0; j < 10; j++) { str_append(str, str_int(j)); } } printf("%d", GetTickCount()-start); } 429 350 424 372 334 348 385 330 364 426 401 378.4545 50 49 50 48 48 50 49 48 48 49 49 48.90909
×