Jump to content

IllidanS4

Uživatel
  • Příspěvků

    70
  • Registrován

  • Aktivní

  • Vítězných dnů

    15

Příspěvky posted by IllidanS4

  1. Je už to pár let, co jsem sem nepsal, ale server se od té doby stále aktualizoval a je plný nových možností, funkcí a příkazů! Pro podrobnější novinky doporučuji sledovat Discord, ale i tak sem dám shrnutí toho nezajímavějšího za poslední 4 roky:

    • Objekty vytvořené v jednom světě se neukazují v jiných.
    • Objekty a vozidla mají své vlastníky a jen oni je mohou ukládat.
    • Na server se lze připojit se všemi verzemi 0.3.7, ale i s 0.3.DL a UG-MP.
    • Kromě klasických NPC lze vytvářet i chodce, kteří mohou používat zbraně, přesouvat se po mapě nebo jezdit ve vozidle.
    • Je možné zablokovat si efekty ostatních příkazů, např. zpráv, PM nebo explozí.
    • Sněžná mapa San Andreas (a další varianty včetně trávy) lze nyní lokálně zapnout i pro hráče, bez nutnosti vstupu do světa.
    • Bylo přidáno menu pro editaci textu na objektech a také menu pro prohlížení všech textur ve hře.
    • Po letech čekání byly přidány závodný minihry!
    • Kromě více než 30 závodních miniher je také možnost vygenerování náhodné minihry se startem kdekoliv v San Andreas.
    • Místo příkazů a maker se pro skriptování na serveru dá mimo jiné i použít jazyk Lua.
    • A poslední novinka, 12 tisíc náhodných chodců všude po San Andreas!
    • Paráda! (+1) 1
    • Líbí se mi to! (+1) 1
  2. Aktualizace: Plugin je momentálně ve verzi 1.4 (doporučuji sledovat GitHub, jak může být patrné z toho, jak často píšu sem). Upravil bych název vlákna, ale nějak na to nemohu najít tlačítko.

    Od verze 0.8 byla přidána hromada věcí, např. hookování nativních funkcí (napříč skripty i pluginy), spojové seznamy, nový typ kolekce (pool), počítání referencí u některých objektů, systém zpracovávání chyb, uspořádané mapy, podpora regulárních výrazů a různých kódování, podpora "reflexe" nad skriptem, tedy čtení různých lokálních proměnných ze zásobníku i volání neveřejných funkcí podle jména, tvorbu výrazových stromů s vlastním parserem výrazů, konfigurovatelný str_format (vlastní specifikátory) atd.

     

    • Děkuji (+1) 1
    • Líbí se mi to! (+1) 2
  3. 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í).
    • Líbí se mi to! (+1) 2
  4. 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á).

  5. 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.

  6. před 32 minutami, xhunterx said:

    To je celkom rozsireny mytus. V sampe nieje problem adresovat akukolvek pamat celeho procesu, ak si ochotny pouzivat #emit alebo v novej verzii __emit()

    Zaujimave. Skoda ze nieje videt, ktora cast tak dlho trva, ci to vytvaranie alebo vyhladavanie. Lebo v praxi ta zaujima iba TryCustomCommand, nie pridavanie, co sa stane len pri nacitani FS :d Ale teda ono je to dost zaujimave tieto rychlosti, ze asi naozaj pojde o use-case. Len teda to sa blbo obhajuje, ono vacsinou ludi ako ATomasa a uprimne aj mna zaujima skorej najhorsi pripad. Pretoze ak su sytuacie, v ktorych je tvoj plugin podstatne pomalsi, tak to podstatne spomalenie moze sposobovat lagy, koly jednojadrovosti sampu.

     

    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.

  7. před 5 hodinami, xhunterx said:

    Tomuto nechapem, ved moj benchmark bol o spojovani dynamickych retazcov. Pointa bola, ze veci ako meno mali byt vratene funkciou napr String:Name(playerid).

    Š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.

    před 7 hodinami, xhunterx said:

    To je velmi pekne v teorii. Lenze tvoj pristup pravdepodobne sposoby vazne problemy s fragmentaciou pamate. Takze v konecnom dosledku mozes zabrat ovela viac RAM nez keby si pouzil staticke polia, zvlast ked ide o lokalne premenne.

    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.

    před 7 hodinami, xhunterx said:

    Ukladanie velkosti verzus pocitanie je stara debata, ktoru asi nevyriesime tu. Ale urcite to nieje vseobecna vyhoda, pretoze tu dlzku musis pocitat pri zmenach, coz moze byt pomalsie nez ju spocitat az ked ju potrebujes. Taktiez vacsina funkcii ako strcat by nemali priamo volat strlen ale zistit dlzku pri praci

    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ší.

  8. před 47 minutami, xhunterx said:

    Takze sme upustili od tej syntaxe s + a vlastne sa len zbavujeme velkosti hej? Aspom by si mohol spravit returnujuci format: String:str_rformat(fmt[], ...);

     

    Ako suhlasim s tym, ze 1.5x pomalsi format nicomu moc neublizi, len fakt moc nevidim tu utilitu, zvlast ked syntax mi pride zlozitejsia nez original.

    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.

  9. On 13. 3. 2018 at 10:49, xhunterx said:

     

     

    Tak hned tvoj prvy priklad toto pouziva ako ekvivalent formatu. A ano, bolo mi jasne, ze toto bude pomalsie, nicmenej preto som to napisal. Lebo som vedel, ze toto bude velmi zle na zlozitost a zaujimalo ma ako moc zle.
    A aj ked priznavam, ze moj kod je asi najhorsi realny pripad, tvoj kod co testujes je pravdepodobne najlepsi pripad pre teba. Ako kde v praxy by si pouzival nieco hentake? Zretazit 10 cisel?

    Uprimne jedina vec, co ma zatial naozaj zaujala na tomto tvojom plugine (string casti) je to, ze by public funkcie mohli returnovat stringy...

    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;
    }

     

  10. 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ší.

  11. 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.

  12. před 14 minutami, ATomas said:

    Tak pokud ten sa-mp furt jede na jednom vlakne. Tak me vykon celkem zajima :d Hlavne se strimgem pracujes kazdou chvili. Ale jako jo mas pravdu na serveru s 20ti lidma moc vykon asi resit nebudes :d

    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
  13. Normální funkce (a nejen stock tedy) umí vracet řetězec z toho důvodu, že kompilátor ve skutečnosti té funkci přidá skrytý parametr označující cílovou adresu, kam se má ten řetězec zkopírovat. Funguje to, protože kompilátor ví, že to udělal takhle, a při každém volání může alokovat potřebné místo, i když o tom programátor neví. U veřejné funkce by musela být omezená velikost toho, co se může vrátit (normální funkce ji taky mají omezenou, ale implicitně podle toho, co se vrací) a s tím by se pak pracovalo ještě hůř a ještě k tomu by to způsobalo zmatek při externím volání (neviditelné parametry nejsou zrovna zřetelné). Tak či tak, parametr navíc tam bude vždy, pokud je třeba vrátit řetězec.

    Jiná možnost je použít PawnPlus a vracet dynamické řetězce. ;-)

  14. před 3 hodinami, ATomas said:

    To asi nefunguje na principu vice vlaken (procesoru) ze? Zkosel jsem si vytvorit moznost vice vlaken pomoci C++, ovsem vzdy mi to druhe vlakno proste cekalo az se dokonci akce na vlakne prvnim. Kdyz jsem odstranil to cekani, tak svr crashnul.

    Nová vlákna to nevytváří; radši bych skutečné paralelní programování v AMX nedělal, protože to na to moc není stavěné. Teoreticky by asi šlo nechat výpočty v AMX běžet na jiném vlákně, ale všechna volání nativních funkcí by musela být synchronizována s hlavním. Otázkou je, jestli to za tu námahu stojí.

    Všechna obnovení z čekání (po wait_ticks, wait_ms) jsou vyvolána v rámci serverových tiků v hlavním vlákně. wait_ms je mimochodem přesnější než SetTimer.

    před 3 hodinami, ATomas said:

    Jinak nejake rychlostni testy? Celkem by me zajimalo jestli je to rychlejsi nez postaru. Protoze jestli ne, tak bych rozhodne neobjetoval pomalost scriptu vymenou za pohodlnost programatora :)

    Práce s řetězci je rychlá stejně jako s std::string v C++ (+ volání nativních funkcí). Testy jsem zatím nedělal, ale až na vytváření a mazání operace zrychlit nepůjdou.

    před 2 hodinami, vEnd said:

    kdy to plánuješ vydat?

    Dodělám dokumentaci, vše zkontrolují a vydám první verzi. Zbývá provést trochu lepší testování, ale zatím nevím o žádných chybách.

  15. před 4 minutami, xhunterx said:

    No znie to zaujimavo. Nicmenej by si mohol pridat k tomu pluginu aj nejaku licenciu, aby ho bolo mozne pouzivat :)

    Na GitHubu máš přeci MIT. ;-)

    před 4 minutami, xhunterx said:

    PawnPlus uz je dost pouzivane meno projektu [1, 2]. Mozno by si mohol vybrat nieco vhodnejsie, ako StringPlus.

    Hledal jsem projekty se stejným jménem, ale naštěstí ani jeden z toho není plugin.

    • Líbí se mi to! (+1) 1
  16. Tu sekci tam mám v podstatě jen na ty dvě funkce, pawn_call_native a pawn_register_callback (a ta "reflexe" v tom je prakticky jen to, že jméno funkce je řetězec). Mohlo by být zajímavé třeba analyzovat různé debugovací informace ve skriptu apod. pro nějaké užitečné věci, ale nenapadá mě moc příkladů využití.

    Teď mě mimoděk napadla funkce pawn_create_arglist, která by si uložila seznam různorodých argumentů a mohla by ho předat do (nativní) funkce, která očekává proměnný počet argumentů.

    stock MyPrint(const format[], ...)
    {
        new ArgList:args;
        pawn_create_arglist(args, 1); //začátek argumentů současné funkce
        printf(format, args);
        pawn_destroy_arglist(args);
    }

    V případě variadických funkcí jsou všechny argumenty předané referencí, takže pokud by si pawn_create_arglist zapamatovala adresu proměnné args, sloužila by ta jako unikátní identifikátor. Jen je nutno takovou adresu zase odregistrovat.

  17. Zbytečnost. Kdo se bojí reklamy (a zmínka IP adresy jiného serveru skutečně reklama není), měl by zapracovat na originalitě vlastního serveru. Dobří hráči zůstávají, špatní hráči chybět nebudou. Ke skriptu samotnému se radši vyjadřovat ani nebudu, ale doporučuji přečíst si něco o složitosti algoritmů.

  18. Já to vyřešil dodatečnou tabulkou řetězců, do níž zkopíruji to, co chci předat, a předávám jenom číslo řádku v té tabulce (který poté zase odstraním). Omezuje to sice délku řetězce a zvyšuje velikost kompilátu, ale funguje to, jak má.

    Otázka ze zvědavosti: na co je ti dobré mít funkci, která zabanuje hráče po sekundě?

×
×
  • Create New...