MMO-k és az Ő addonjaik

Már az idejét sem tudom mikor kezdtem el foglalkozni az MMORPG kel, de azóta rengeteget tanultam róluk. Mondhatom szerencsémre.
Jelenlegi írásomban nem a játékokról, hanem a hozzájuk tartozó addonokról szeretnék pár szót szólni. Eléggé sok MMO val volt már dolgom az elmúlt években, ezek közül természetesen mi lehetne az, ami a legtöbb időmet elvette és a mai napig is szép emlékeket társítok hozzá? (nem, nem a Hello Kitty Online) Természetesen a World Of Warcraft. Eme nagyszerű játék volt az első az életembe, Ő volt, aki behúzott az MMO-k, a Raidek, a BG-k világába, és Ő volt az, aki megismertette velem az Addonok-at is.

Azt hiszem mondhatom azt, hogy a WoW volt az alapozó, hisz legyen szó bármilyen MMO ról, az addon rendszer szinte teljesen ugyan az, mint a WoW -é. Ugyan az a scriptnyelv, ugyan azok a függvények, ugyan az a felépítés, így nem lenne csoda, ha az addonok minden ilyen stílusú játékban gomba mód szaporodnának, de ellenben az elvárással, a mai napig a WoW az, aki hatalmas addon adatbázissal rendelkezik, míg a többiek több ezerrel szorulnak mögé!

Egy nagyon hasznos oldal a Curse.com, ami az általam ismert legjobb, legigényesebb, legjobban használható és legnagyobb addon adatbázis, a WoW -nak 5.001, a 2. helyezett Warhammer Online nak pedig 199 addonja van!

A mostani addon fabrikálást egy régi WoW os addonommal fogom bemutatni, amit annak idején még drága barátom Taszi unszolására csináltam a Burning Crusade idején (de régen is volt … :))
Azóta sok víz folyt le a Dunán, de remélem még ma is működik.

Az addon neve: CritKing (WoW link: http://www.curse.com/addons/wow/crtkng, Rift link: http://www.curse.com/addons/rift/riftcritking)

Az alap működése az, hogy számolja a kritikus ütéseket és az egymás utáni győzelmeket egy harcban és annak megfelelő üzenetet ír ki, vagy épp játszik le egy hangot. Jelenleg Runes Of Magic -hez készül a legfrissebb verziója.

Scriptnyelv

Az addonok nyelve a LUA. Erről a nyelvről volt már szó itt a blogomon ( #1 #2 ), én kifejezetten szeretem, hisz nagyon könnyen használható, jól átlátható, és ha az ember sikeresen beintegrálja a C/C++ kódjába (vagy használ valami egyszerű interfacet), akkor már az összeköttetés és oda vissza hívás is egyszerű és kényelmes.

Talán ez az oka annak, hogy a Blizzard is (és utánna mások is) ezt a nyelvet választották.

 

FrameXML

http://www.wowwiki.com/FrameXML

Na ez nem scriptnyelv, viszont annál nagyobb találmány. A modern MMO-k GUI interface ét nem úgy készítik, hogy egy grafikus megrajzolja az egészet, majd egy texturaként bekerül a játékba és láss csodát. Szerencsénkre! A megoldás a FrameXML, ami nem más, mint egy XML fájl, amiben leírják a GU-t és a hozzá tartozó vezérlő függvényeket, amik LUA ban vannak megvalósítva. Ezáltal az addon fejlesztők is szabadon módosíthatják, alakíthatják a GUI-t, sokszor teljesen átszabva a felületet! Illetve ezzel a módszerrel könnyűszerrel hozhatunk létre új GUI elemeket, növelve ezzel a játék értékét és használhatóságát.

TOC fájl

http://www.wowwiki.com/TOC_format

A TOC fájl egy speciális fájl, ami az addonnal kapcsolatos információkat tárolja. Sok MMO ebben a fájlban kisebb eltérésekkel rendelkezik, de mindegyik ugyan arra használja. Ez a fájl mondja meg a nevet, verziót, betöltendő fájlokat, stb. (Ez a Runes Of Magicre pont nem igaz, mert csak a betöltendő fájlokat tárolja, ott nincs Addon Manager interface)

Megkötések

Általánosságban nehéz bármit is mondani, hogy milyen megkötései vannak az addonoknak, de az biztos, hogy sok minden ki van kötve belőlük, biztonsági okokból. Például egyik sem tud fájlt létrehozni, törölni, módosítani, stb. Nem képesek hálózati kapcsolatot nyitni, vagy alkalmazásokat elindítani. A Blizzrdnál az addonok nem is láttak ki a telepítés könyvtárából (World of Warcraft telepítési könyvtára).

Ezek ugye a LUA moduljainak menedzselése, azaz nem töltik be az OS, IO modulokat, ezáltal a fejlesztők nem férnek hozzá ezekhez a kényes függvényekhez.

Minden más megkötés már nagyon játék függő, attól függően, hogy az adott kiadó milyen megszorításokat ír elő a farmerek és botok ellen, illetve nagyban korlátozza az addonokat, hogy ki mennyi energiát és erőforrást áldoz a belső függvények kivezetésére (a legszerényebb az Allods Online volt, főleg, hogy a dokumentáció is orosz volt :))

Lecsó

Na de vágjunk is akkor bele, mert azt ígértem, hogy itt addonban fogunk taposni! Na azért nem 🙂

CRITKINGPREFIX = "CK"
CRITKINGVERSION = "0.1.0"

CritKing = {} -- critking class :)

CritKing.__index = CritKing

CritKingDisplay = "on" -- saved variable, per character
CritKingResetOnNormalHit = "on" -- reset crit statistic when hit normal

CritKing.VariablesLoaded = false

CritKing.PlayerName = ""  -- the player's character name

CritKing.MaxDamage = 0        -- The maximum damage
CritKing.MaxCritNum = 0       -- The maximum crit number
CritKing.Damage = 0           -- The actual damage
CritKing.CritNum = 0          -- The actual critical number
CritKing.KillNum = 0          -- The actual killing blow number
CritKing.MaxKillNum = 0       -- The maximum killing blow number

CritKing.CritMessages =
{
    "Head shot!"        -- 1
  , "Oh, Yeah!"         -- 2
  , "Unstoppable!"      -- 3
  , "Killing Spree!"    -- 4
  , "Dominating!"       -- 5
  , "Ultra Kill!"       -- 6
  , "Wicked Sick!"      -- 7
  , "God Like!"         -- 8
  , "God Like!"         -- 9
  , "Holy Shit!"        -- 10
  , "Holy Shit!"        -- 11
  , "Holy Shit!"        -- 12
  , "Holy Shit!"        -- 13
  , "Holy Shit!"        -- 14
  , "Monster Kill!"     -- 15
  , "Monster Kill!"     -- 16
  , "Monster Kill!"     -- 17
  , "Monster Kill!"     -- 18
  , "Monster Kill!"     -- 19
  , "Ludicrous Kill!"   -- 20
  , "Ludicrous Kill!"   -- 21
  , "Ownage!"           -- 22
}

CritKing.CritSounds = 
{
    "Interface\\Addons\\CritKing\\sounds\\head_shot.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ohyeah.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\unstoppable.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\killer.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\dominating.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ultrakill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\wickedsick.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\godlike.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\godlike.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ludicrouskill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ludicrouskill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg"
}

CritKing.KillingMessages =
{
    "First Blood!"          -- 1
  , "Double Kill!"          -- 2
  , "Tripple Kill!"         -- 3
  , "Multi Kill!"           -- 4
  , "Ultra Kill!"           -- 5
  , "Mega Kill!"            -- 6
  , "Monster Kill!"         -- 7
  , "Ludicrous Kill!"       -- 8
  , "Wicked Sick!"          -- 9
  , "Holy Shit!"            -- 10
  , "God Like!"             -- 11
  , "Ownage!"               -- 12
  , "Ownage!"               -- 13
  , "Ownage!"               -- 14
  , "Ownage!"               -- 14
  , "Ownage!"               -- 15
  , "I am INVICIBLE!"       -- 16
  , "I am INVICIBLE!"       -- 17
  , "I am INVICIBLE!"       -- 18
  , "I am INVICIBLE!"       -- 19
  , "YES! I'm a GOD!"       -- 20
}

CritKing.Killingsounds =
{
    "Interface\\Addons\\CritKing\\sounds\\firstblood.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\doublekill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\tripplekill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\multikill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ultrakill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\megakill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\monsterkill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ludicrouskill.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\wickedsick.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\holyshit.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\godlike.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\ownage.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\invicible.ogg"
  , "Interface\\Addons\\CritKing\\sounds\\god.ogg"
}

CritKing.MaxCrit = 23
CritKing.MaxKill = 21

-- Simple message function, to the default chat frame
function CritKing.SendMsg( msg )
    DEFAULT_CHAT_FRAME:AddMessage( CRITKINGPREFIX .. ": " .. msg, 1.0, 0.0, 0.0 )
end

-- Command handler
function CritKing.OnCommand( args )

    if ( ( args ~= nil      )
     and ( string.len( args ) > 0 ) )
    then
        if ( string.lower( args ) == "help" )
        then
            CritKing.SendMsg( "/ck display on  - enable error frame messages" )
            CritKing.SendMsg( "/ck display off - disable error frame messages" )
            CritKing.SendMsg( "/ck normal on - reset critical statistic when hit normal and end of fight" )
            CritKing.SendMsg( "/ck normal off - reset critical statistic just when end of fight" )
            CritKing.SendMsg( "/ck help - display this text" )
            CritKing.SendMsg( "/ck - display max damage, critical number, killing blow statistic" )
            return
        end

        if ( string.lower( args ) == "display on" )
        then
            CritKingDisplay = "on"
            CritKing.SendMsg( "set display on" )
            return
        end

        if ( string.lower( args ) == "display off" )
        then
            CritKingDisplay = "off"
            CritKing.SendMsg( "set display off" )
            return
        end

        if ( string.lower( args ) == "normal on" )
        then
            CritKingResetOnNormalHit = "on"
            CritKing.SendMsg( "reset critical statistic when hit normal" )
            return
        end

        if ( string.lower( args ) == "normal off" )
        then
            CritKingResetOnNormalHit = "off"
            CritKing.SendMsg( "no reset critical statistic when hit normal" )
            return
        end
    end     
    -- no parameters given, show the infos
    CritKing.SendMsg( " - Max damage: " .. CritKing.MaxDamage )
    CritKing.SendMsg( " - Max crit num: " .. CritKing.MaxCritNum )
    CritKing.SendMsg( " - Max kill num: " .. CritKing.MaxKillNum )
end

-- Fired when the player hit critical
function CritKing.OnCrit()
    local msg = CritKing.CritMessages[ CritKing.CritNum ]
    local sound = CritKing.CritSounds[ CritKing.CritNum ]
    
    if ( CritKingDisplay == "on" )
    then
        UIErrorsFrame:AddMessage( msg .. " (x" .. CritKing.CritNum .. ") Damage: " .. CritKing.Damage )
    end

    PlaySoundFile( sound )
end

-- Fired when the player kill an enemy
function CritKing.OnKill()
    local msg = CritKing.KillingMessages[ CritKing.KillNum ]
    local sound = CritKing.Killingsounds[ CritKing.KillNum ]
   
    if ( CritKingDisplay == "on" )
    then
        UIErrorsFrame:AddMessage( msg .. " (x" .. CritKing.KillNum .. ")" )
    end
    
    PlaySoundFile( sound )
end

-- Init function
function CritKing.OnLoad(self)
    self:RegisterEvent( "COMBAT_LOG_EVENT_UNFILTERED")
    self:RegisterEvent( "PLAYER_REGEN_DISABLED" )
    self:RegisterEvent( "PLAYER_REGEN_ENABLED" )
    self:RegisterEvent( "CHAT_MSG_COMBAT_HOSTILE_DEATH" )
    self:RegisterEvent( "VARIABLES_LOADED" )

    SlashCmdList[ "CRK_CMD" ] = CritKing.OnCommand
    SLASH_CRK_CMD1 = "/ck"
end

-- Reset critical statistric
function CritKing.ResetCrit()
	CritKing.CritNum = 0
end

-- Reset killing blow statistic
function CritKing.ResetKill()
	CritKing.KillNum = 0
end

-- Reset all statistic
function CritKing.ResetStat()
    CritKing.ResetCrit()
    CritKing.ResetKill()
end

-- Event handler
function CritKing.OnEvent( self, event, ... )
    if ( event == "VARIABLES_LOADED" ) -- loading saved variables, and player name
    then
        CritKing.VariablesLoaded = true
        CritKing.PlayerName = UnitName( "player" )
	CritKing.SendMsg( "variables loaded! " .. CRITKINGVERSION )
        return
    end

    if ( CritKing.VariablesLoaded == false ) -- no event handling, while saved variables not loaded ...
    then
    	return
    end
	
    if ( event == "COMBAT_LOG_EVENT_UNFILTERED" ) -- new event from combatlog
    then
	local timestamp, eType, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags  = ...;
        if ( sourceName == nil )
        then
            sourceName = ""
        end

	if ( sourceName ~= CritKing.PlayerName ) -- if the sender not equal with the player, just return
        then
            return
        end
        
	local fromNum = 9;
	if ( eType == "SPELL_DAMAGE" )
	then
		fromNum = 12;
	end
		
	local amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing = select(fromNum, ...)
        if ( critical == nil )
        then
            critical = false
        end
       
	if ( eType == "PARTY_KILL" )
        then
            CritKing.KillNum = CritKing.KillNum + 1
		    if ( CritKing.MaxKillNum < CritKing.KillNum )
            then
                CritKing.MaxKillNum = CritKing.KillNum
            end

            if ( CritKing.KillNum < CritKing.MaxKill )
            then
                CritKing.OnKill()
            end
    
            return
        end
    
        if( critical )
        then
            CritKing.CritNum = CritKing.CritNum + 1
            CritKing.Damage = amount

            if ( amount > CritKing.MaxDamage )
            then
                CritKing.MaxDamage = amount
            end
            
            if ( CritKing.CritNum > CritKing.MaxCritNum )
            then
                CritKing.MaxCritNum = CritKing.CritNum
            end
            
            if ( CritKing.CritNum < CritKing.MaxCrit )
            then
                CritKing.OnCrit()
            end
            else
            if ( CritKingResetOnNormalHit == "on" )
            then
                CritKing.ResetCrit()
            end
        end

        return
    end
    
    if ( ( event == "PLAYER_REGEN_ENABLED" )   -- player enter, or leave from combat
      or ( event == "PLAYER_REGEN_DISABLED" ) )
    then
	    CritKing.ResetStat()
    	return
    end
end

Mondhatnám, hogy ennyi dióhéjban 🙂
De azért nézzük meg közelebbről, ha már ilyen szépen sikerült ide tennem ezt a pár oldalnyi borzalmat.

A kód eleje nem más, csak adat feltöltés, ahol megadom a hangokat és szövegeket, ez eléggé egyszerű. Habár kitérnék arra a kicsit csúnyaságra, hogy kézzel adom meg a tömb elemeinek számát

CritKing.MaxCrit = 23
CritKing.MaxKill = 21

Az oka egyszerű, a table.getn nem akart működni 🙂

Az első függvény, ami fel fog hívódni az OnLoad( self )
Rift ben a CritKing:RegisterEvent( Event.Addon.Load.End, OnLoad ) ot kellett felhívni, ahol az OnLoad az addon nevét kapta meg, míg Runes Of Magic -ben a FrameXML ben kellett megadni, hogy mi az OnLoad függvény, ahol a self helyett this -t kapott.

Apró eltérések, de érdemes feljegyezni Őket, ha valaki portolható addont akar készíteni.

Az OnLoad ezután regisztrálja az eventeket es a Slash commandot (mindjárt kitérek a Slash commandokra). Ez WoWban és ROM ban szinte teljesen ugyan az, míg a Rift ben a
CritKing:RegisterSlash( “crtk”, EvntSlash ) felhívásával értük el ugyan azt. Ez is csak apró különbség.

Alapvetően a Rift annyiban tér el a WoW és ROM tól, hogy nála egy table ban vannak az események, és ehhez adjuk hozzá a mi függvényünket is, míg a másik kettőben az eventeket stringes nevükkel regisztráljuk be.

WoW events: http://www.wowwiki.com/Events_%28API%29
ROM events: http://www.theromwiki.com/List_of_Events
Rift events:   http://wiki.riftui.com/Event

A további részek már egyszerűek, csak kezelni kell a beérkező eseményeket. Természetesen a Rift, ROM és WoW teljesen más sorrendben, teljesen másképp közli a számunkra fontos dolgokat, erre is lehet írni egy kellemes interfacet és már el is van fedve.

Egyszer ha igény / energia lesz rá kitérünk erre is 🙂

Slash Command

Ígértem, hogy írok erről is, hogy micsoda. A Slash command, ahogy a neve is mutatja a / es parancsok felsorolása, amit az addonunk kezel. Ez mindenhol megegyezett, hogy így kezelik a chatben a parancsokat, a regisztrációjuk, ahogy már említettem más és más. A lényeg viszont szintén ugyan az, 1 függvény, ami kap 1 bemenő paramétert, ami a string, a parancsunk után. Ezt kell értelmezni és lehet különféle műveleteket végezni.

Addon dokumentációk:

WoW: http://www.wowwiki.com/World_of_Warcraft_API
ROM:

Addon Tutorial: http://www.theromwiki.com/Addon_Tutorial
Eventek: http://www.theromwiki.com/List_of_Events
Függvények: http://www.theromwiki.com/List_of_Functions

Rift: http://wiki.riftui.com/Main_Page

Addon gyűjtemény: http://www.curse.com

Most ennyi lett volna, de ugye megint csak lehet zavarni a kommentekkel, annak, akit érdekel a téma 🙂

XML-RPC

 

Következő bejegyzésemben egy számomra érdekesnek tartott témáról szeretnék némi felvilágosítást adni. Ez nem Airsoft lesz, hanem programozás, amit lehet sokan nem szeretnek, ettől függetlenül remélem érdekes és tanulságos lesz azoknak, akik ezzel foglalkoznak.

A mai téma az XML-RPC lesz, ami 2 rövidítés, méghozzá az Extensible Markup Language és a Remote Procedure Call. Hogy mik is ezek, hát az jó kérdés, megpróbálok külön külön választ adni és egyben, hogy mi is azaz XML-RPC szerver és hogyan is kellene csinálni egyet. Mindezt C# ban, mert az könnyű és nyugos, ráadásul kész van egy nap alatt.

 

Kezdjük az XML el

Az XML-t alapvetően arra találták ki, hogy egy olyan fájlt hozzanak létre, amivel bármikor bármit le tudnak írni. Képes bármilyen adathalmaz reprezentálására, úgy, hogy a programoknak nem szükséges ismerniük a pontos felépítést, mindössze az XML szabályoknak kell megfelelnie.

Rengeteg felhasználási helye van az XML nek és valószínűleg már mindenki találkozott vele valamilyen programnál, amit használt, ha máshol nem akkor a windowsban, ami maga is rengeteg XML fájlt használ adatok és beállítások tárolására.

Egy egyszerű XML fájl:

<?xml version="1.0" encoding="iso-8859-1"?>
<XmlTest>
   <XmlValue name="nameofvalue" value="valueofvalue">
       <![CDATA[Special text]]>
   </XmlValue>
</XmlTest>

Az XML nagy előnye, hogy a saját szabályai alapján valóban bármilyen adat struktúrát le lehet benne írni, ezáltal egy teljesen általános adat tároló eszközt kapunk, amihez ma már rengeteg XML olvasó beszerezhető, illetve nem tart kifejezetten sokáig egy sajátot írni, főleg, ha a szabályok nagy részétől eltekintünk, ami legtöbbször könnyedén elengedhető egy saját project esetén.

 

Az RPC

Hát az RPC ről sokat nem is lehet mondani, hisz ez egy … talán fogalom. A lényege, hogy egy számítógépen valami módon kiadunk egy utasítást, ami végrehajtódik egy távoli számítógépen. Természetesen mielőtt bárki összeesküvés elméleteket gyártana, itt nem arról van szó, hogy fogom a Windows Notepadet és valami speciálsi billentyű kombinációval egyből lehet valaki más gépét irányítani.

Az RPC utasításokat a szerveren regisztrálják, a hozzá szükséges kliensen pedig csak ezeket lehet felhívni. Persze hiba mindig lehet a gépezetben, de feltételezzük, hogy nincs 🙂

 

XML-RPC?

Na és akkor hogy is jön ki a kettőből az XML-RPC? Egyszerűen. Itt a kettő a kommunikáció mikéntjét és a felhasználást jelöli, azaz XML ben utasítjuk a szervert a távoli eljárások felhívására. Az XML ről ugye már megbeszéltük, hogy bármilyen adat struktúrát le tud írni, így egy tetszőleges utasítást és annak paramétereit is le tudjuk írni, illetve az eljárás visszatérési értékét.

Ehhez persze az XML-RPC nek kell egy szabvány, ami megmondja, hogy az XML nek milyen formátumúnak kell lennie, hogy azt minden kliens és minden szerver valóban megértse. Ez a szabvány természetesen csak útmutatás, nem kötelező etz használni, csak akkor már nem beszélhetünk szabványos XML-RPC szerverről. Hogy ez miért fontos, mindenki döntse el magában.

Megjegyzem, hogy a WordPress is képes XML-RPC kommunikációra, ami lehetővé teszi saját WordPress szerkesztők írását (mint a Windows Live Writer), ami könnyedén tud kommunikálni a WordPress oldallal és feltölteni a legújabb bejegyzéseket, média fájlokat, stb.

Hogy mire is jó az XML-RPC valójában? A válasz egyszerű. EZ egy általános kommunikációs forma, 2 számítógép között, ahol nem számít a számítógépek felépítése és a rajtuk futó operációs rendszer típusa. A kliens egyszerűen a megadott formátumban közli a másik számítógéppel, hogy milyen függvényeket szeretne felhívni, az pedig képes válaszolni, hogy ezen függvényeknek mi volt a kimenete.

Természetesen van hátránya a rendszernek, nem is kevés. Először is az XML ugye nagyon nem tömör formátum, így a hálózati kommunikáció a sokszorosára dagad. Az XML további hátránya a feldolgozási sebessége. Jóval lassabb egy XML fájlt feldolgozni, mint egy saját bináris típust.

Egy másik hátrány maga a kommunikáció. Szabványos, HTTP kommunikáció, ami megkönnyíti az esetleges visszaéléseket, próbálkozásokat, illetve nehezíti a szerver fejlesztők munkáját ezek kivédése.

Specifikáció: http://xmlrpc.scripting.com/

Egy kérés példa:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
  <methodName>examples.getStateName</methodName>
  <params>
    <param>
      <value><i4>41</i4></value>
    </param>
  </params>
</methodCall>

A szerver

Na de csapjunk most már bele a lecsóba kicsit. Írjunk XML-RPC szervert!
Ehhez én a C# ot választottam. Tudom, hogy nem a legoptimálisabb, nem a leggyorsabb (habár nem is olyan borzalmasan lassú, mint a Java), viszont rengeteg minden meg van már benne írva, amivel nem kell foglalkozni, csak használni, ezáltal egy több szálas, TCP / SSL kommunikációval működő szerver elkészítése nem több 1 könnyed napi munkánál. Azon esetekben ahol az XML-RPC jó megoldás és nem több tízezer felhasználót kell kiszolgálni másodpercenként, bőven elég lehet egy ilyen megoldás.

Amellett a C# most nagyon divatos 🙂

No, de kezdjük is el. Az első dolog, amire nem árt figyelni, hogy lehetőleg olyan kódot írjunk, amit a Mono is képes lefordítani, így C# ban is elmondhatjuk, hogy multi platform kódot írtunk, ráadásul jobb esetben még akár Unity3D ben is felhasználhatjuk, hisz az is Mono val fordít.

Hogy ki hol kezdi, az egyedi, én az XML felolvasóval kezdtem.

XML reader

A beépített System.XML túl rengeteg letölthető minimális XML olvasó található, vagy akár írhatunk mi is egyet saját kezűleg. Ez utóbbit inkább csak akkor javaslom, ha sok időnk van és szeretnénk ezzel szórakozni, máskülönben egyszerűbb leszedni egy működő minimál kódot. Részemről egyszerűen letöltöttem ezt: SmallXMLParser. Apró módosításokkal teljesen személyre szabható és optimalizálható.

Felmerülhet a kérdés, hogy mi értelme ennek. A válasz viszont annyira nem egyszerű. Ha kizárólag Windows ra fejlesztünk, akkor nem sok értelme van, hisz ott a System.XML, amit nyugodt szívvel használhatunk, hisz ott a Windowsban mindig. Ám mi van akkor, ha több rendszerre szeretnénk eljutni, vagy ha Unity3D vel akarjuk kombinálni. Ezen esetekben nem biztos, hogy van System.XML.dll ünk, vagy egyszerűen csak nem akarjuk letölteni az egész DLL-t. Ugyancsak a SmallXMLParser mellett szól, hogy jóval kevesebb dologgal foglalkozik mint a minden szabályt figyelembe vevő XML olvasók, ezáltal jóval gyorsabb lehet a kód!

Persze ha tudjuk, hogy egészen pontosan milyen platformra fordítunk, felmerülhet a natív kód alkalmazása, azaz C/C++ ban írhatunk saját XML olvasót, de nagyon figyeljünk, hogy a Natív – Managed kód váltás is nagyon erőforrás igényes tud lenni!

Szerver

Nem tudom nem-e túlzás szervernek nevezni mindazt, ami C# ban ahhoz kell, hogy egy porton figyeljünk és adatokat fogadjunk. Rettentően egyszerű dolgunk van, mert mint írtam a C# ban már minden készen van! Míg C/C++ ban komoly szenvedés tud lenni egy socket és az SSL layer összehozása, addig C# ban egy egyszerű beállítás (majdnem :))

public bool Start( IPAddress aIP, int aPort, IServiceProvider aServiceProvider, String aCertFile )
{
    ServiceProvider     = aServiceProvider;
    Stopping            = false;

    mConnectionCallback = new AsyncCallback( OnConnectionHandler );
    mAcceptCallback     = new WaitCallback( OnAcceptHandler );

    mListener           = new TcpListener( aIP, aPort );

    try
    {
        mListener.Start();
        mListener.BeginAcceptSocket( mConnectionCallback, null );
    }
    catch /*( System.Exception ex )*/
    {
        return false;
    }

    if ( ( aCertFile != null )
      && ( File.Exists( aCertFile ) ) )
    {
        if( !SSLInitialized )
        {
            ServicePointManager.ServerCertificateValidationCallback += ( s, cs, ca, p ) => true;
            SSLInitialized = true;
        }

        mCertificate = new X509Certificate2( aCertFile, "mycertpassword" );
        SSLEnabled = true;
    }
    else
    {
        SSLEnabled = false;
    }

    return true;
}

Íme. Egy nagyon egyszerű kódocska, ami alig 1-2 érdekességet azért tartalmaz 🙂
Az egyik ilyen fontos dolog, hogy a szerverben kiüssük az SSL certificate validálását abban az esetben, ha csak titkosítani akarunk, de nincs hiteles SSL Certification ünk.

Az alábbi sorral ezt el is végezhetjük:

ServicePointManager.ServerCertificateValidationCallback += ( s, cs, ca, p ) => true;

Hogy ez mi is? A ServicePointManager.ServerCertificateValidationCallback jéhez hozzá adunk egy új függvényt, ami mindössze annyit csinál, hogy true val visszatér.

http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx
http://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback.aspx

Egyeseknek idegen lehet a ( s, cs, ca, p ) => true így leírva. Ez a C# Lambda Expression. Szépen átláthatatlanná lehet tenni vele a kódot, de azért néha jól tud jönni, ha nem akarunk annyit gépelni 🙂

Nos, a szerverünk már fut is, habár ha csatlakozunk is hozzá sok minden nem történik.
Az, hogy mi van az OnconnectionHandler ben nem írnám le, aki a témával foglalkozik hamar rá fog érezni, de azért 1-2 jó tanácsot leírnék:

Mindenképpen hozzunk létre egy osztályt, ami az új kapcsolat adatait tartalmazza. Socket, ReadBuffer, esetleges hozzá tartozó biztonsági szintek, beállítások, események, így jóval könnyebb lesz kezelnünk egy egy kapcsolatot, vagy felhasználót.

A másik a ThreadPool.QueueUserWorkItem nevű függvény, ami miatt az mAcceptCallback létre lett hozva. Elfogadott, hogy az accept kicsit lassabb, ha már létrejött a fizikai kapcsolat, úgyhogy érdemes az ezzel járó inicializálást akkor elvégezni, amikor “van szabadidőnk”.

Kommunikáció

Van XML ünk és van szerverünk, de még nem dőlhetünk hátra, hisz a hálózati kommunikáció még C# ban sem fenékig tejfel, legalábbis én még nem találtam meg benne azt a beállítást, ami mindent megoldana 🙂

Alapvető probléma, hogy a hálózaton a csomagok még TCP-n sem úgy jönnek, ahogy azokat elküldjük. Nagy könnyebbség, hogy biztosan sorba vannak rendezve és biztosan mindegyik megérkezik, de abban nem lehetünk biztosak, hogy egy olvasással a teljes elküldött adat mennyiséget felolvastuk.

Hogy kicsit talán érthetőbb legyek, elküldjük az alábbi kérést:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
  <methodName>examples.getStateName</methodName>
  <params>
    <param>
      <value><i4>41</i4></value>
    </param>
  </params>
</methodCall>

Ekkor a szerver ugye észreveszi, hogy jött neki adat csomag az adott hálózati kapcsolaton.
Mi megpróbáljuk felolvasni a teljes adatot, ami ennyi lesz csak:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
  <methodName

Nem az egész XML, amire nekünk szükségünk lenne, ezért várnunk kell és ha megjött a maradék, akkor feldolgozni a kérést.
Ha nem jött le az egész fejléc (aminek a vége a \r\n\r\n) akkor ezt a vezérlő karakter láncot meg kell várjuk. Ha ez megérkezett akkor a Content-length ben megadott mennyiségű bájtot kell még felolvasnunk, ennyi adatnak kell megérkeznie. Ha ezek megvannak, akkor elkezdődhet a feldolgozás.

Procedure regisztráció

Ez nem egy túl bonyolult téma, egyszerűen csak a véleményen osztom itt meg mindenkivel. Én személy szerint szeretem a C# ban létező Attribute osztályokat, ezért egyszerűen írtam egy ilyen osztályt, amit egy class függvényeihez lehet adni.

[ AttributeUsage( AttributeTargets.Method, AllowMultiple = true ) ]
public class RpcCommandAttribute : Attribute
{
    public string MethodName { get; private set;}

    public RpcCommandAttribute( string aMethodName )
    {
        MethodName = aMethodName;
    }
}

Regisztrációkor végig megyek az átadott osztály függvényein és amelyiken van RpcCommandAttribute azt feldolgozom és regisztrálom.

public void Register( IMethod aMethod )
{
  Type MethodClassType = aMethod.GetType();
  System.Reflection.MethodInfo[] Methods = MethodClassType.GetMethods();

  foreach( System.Reflection.MethodInfo Method in Methods )
  {
    RpcCommandAttribute[] Attributes = Method.GetCustomAttributes( typeof(RpcCommandAttribute ), true ) as RpcCommandAttribute[];
    foreach( RpcCommandAttribute CommandAttribute in Attributes )
    {
      MethodCallback Callback = (MethodCallback)Delegate.CreateDelegate( typeof( MethodCallback ), aMethod, Method );
      mRegisteredMethods.Add( CommandAttribute.MethodName, Callback );
    }
  }
}

Itt jegyzem meg, hogy C# ban az Attribute osztályok végéről használatukkor elhagyható az Attribute rész, azaz az RpcCommandAttribute ot használhatjuk RpcCommand ként is!

 

A procedure call

Ez az a rész, ahol multi thread nek kell lennünk. Ugye egy egy függvény futása sokáig is eltarthat (már akár 1 másodpercig is), ezért nem tehetjük meg, hogy egyszerre csak 1 kérést szolgálunk ki. Könnyű a dolgunk, ha nincs olyan rész, ahol a kérések össze akadhatnak, mert akkor nem kell minden féle lockokkal és rw mutexekkel foglalkoznunk.

Azaz ez így nem teljesen igaz, ugyanis. A szerver a feldolgozott kéréseket egy Queue ba teszi, a szálak pedig innen szedik ki és hajtják Őket végre. Ennek a Queue nak thread safe nek kell lennie, máskülönben csúf fagyások elé nézünk 🙂

Egy kényelmes megoldás: ConcurrentStack. Sajnos jön a fekete leves, kizárólag 4.5 ös .Net ben elérhető, ami azt jelenti, hogy a Mono nem ismeri ezt a tároló osztályt. Azért pánikra semmi ok, szerencsére egy egyszerűbb thread safe stack et összedobni C# ban nem több fél óránál, így azoknak, akik Linux / Unity alá is szeretnék lefordítani, ennyit még rá kell áldozni a projektre 🙂

Tehát, a szerver, a feldolgozott RPC hívás információkat egy stack be teszi, a futó worker threadek pedig ezt a stacket figyelik. Ha van benne valami, akkor az első aki hozzáfér kiszedi belőle a kérést és elkezdi végrehajtani, ezáltal lehetőséget adva a többi szálnak is, hogy egy másik kérésen dolgozzanak.

A szálak száma nem végtelen. Nem túl szerencsés a CPU szám + 1 nél többet futtatni, de természetesen ez nagyban függ az eljárások fajtájától. Ha mindegyik rengeteg IO művelet, IO megszakítással, akkor lehet több szál is, hisz IO waitekben sokat állnak, ezáltal adnak lehetőséget a többi szálnak a CPU használatára. Ha minden függvény erősen CPU függő, akkor nem tudunk többet felvenni, mert egymást akadályozzák!

 

Végszó

Íme, nagyon nagy vonalakban ennyi egy XML-RPC szerver (erős túlzással :)), de a lényeget remélem sikerült összefoglalni, illetve pár dologra felhívni a figyelmet, esetleg ötletet adni. Természetesen aki ennél is többet szeretne tudni eme remek dologról, nyugodtan hozzászólhat és leírom, amit csak tudok 🙂

A lényeg, hogy fejlesztés közben mindig mérjük fel, hogy mennyi időnk és erőforrásunk van és ez által tervezzük meg a programunkat a felhasznált programnyelvtől, a felhasznált 3rd Party eszközökön át egészen a megvalósítás apró optimalizációjáig, hogy a lehető legjobb eszközt állítsuk elő, és mindig kódoljunk szépen, átláthatóan, máskülönben a saját kódunkat sem fogjuk felismerni 🙂

Tool fejlesztés I.

Mivel az eddigi legtöbb látogatót hozó bejegyzés technikai volt, úgy döntöttem nem hagyom abba és írok még valamiről, amiről azt hiszem értek hozzá. Remélhetőleg csupa hasznos információval tele tűzdelve. Sajnos sok ilyen leírást nem csináltam még, így, ha bárkinek van javaslata, hogy és mint alakítsam az írásaimat.

Első körben arra gondoltam, hogy a tool fejlesztésről írok, mivel mostanában foglalkozom vele egy kicsit, és már nagyon régóta érdekel a téma. Könnyen lehet azt hinni, hogy a fejlesztés ezen része egyszerű, amit szinte bármilyen programozó meg tud csinálni. Hát szerencsére ezzel ellent kell mondjak. Egy jó toolt sem könnyebb csinálni, mint akár egy játékot.

Kezdjünk is bele …

Először is ki kell választanunk egy program nyelvet, amiben a toolt fejleszteni akarjuk. Ez a kérdés nem feltétlenül egyszerű, habár ma már rendelkezésünkre állnak olyan technológiák, amik azért megkönnyítik a döntést. Az első lépés, hogy kiválasszuk az operációs rendszert, ami alatt a toolt használni akarjuk. Mivel játékfejlesztő vagyok, leginkább a Microsoft Windows rendszer az, ami a környezetemben leginkább elterjedtebb és be kell látni, hogy ez az a rendszer, ami alá a legegyszerűbb és ezáltal a leggyorsabb fejleszteni (gyorsaság = olcsóság). Való igaz, hogy egy Windows 30-80.000 ft ba kerül, de ha azt vesszük, hogy ez az összes egy fejlesztő 1 heti bére, akkor hamar ki lehet kalkulálni, hogy több értelme van akár Windows rendszereket venni, mint hónapokat beleölni egy Linuxos fejlesztésbe. De azért nézzük meg miért is mondom én ezt.

Windows

  • Kiforrott fejlesztő eszközök (Visual Studio, WPF)
  • Könnyed használat
  • Széleskörű software support
  • Kiforrott, nagymennyiségű library, OS support
  • Fejlett ActiveX-es objektumok
  • Széleskörü codec lista
  • Magas generációs programnyelvek (C#, C++)
  • Kiforrott, komoly SDK-k (XNA)

Linux

  • Széleskörü Open Source megoldások
  • Integrált script nyelvek (Bash, Perl, Python)
  • Ingyenesség
  • C# támogatás

Mivel egy ilyen fejlesztés nem arról szól, hogy ki mit szeretne, itt nincs helye Linux – Windows vita. Egyszerű tények alapján kell dönteni, ami legtöbbször oda vezethető vissza, hogy melyik az olcsóbb és mivel a Windows sokkal több és sokkal kiforrottabb lehetőségeket tartalmaz ezért a választás egyértelműen rá esik.

Az OS kiválasztás után ki is lehet választani a program nyelvet, amit használni akarunk. Ezek a döntések ugyan azon alapulnak, mint maga az operációs rendszer választás. Eléggé sok mindent figyelembe kell azonban venni ahhoz, hogy jó döntést hozhassunk.

A kiválasztott nyelv legyen:

  • Magas szintű, hogy könnyen és ezáltal gyorsan lehessen benne fejleszteni
  • Legyen hozzá grafikus designer, hogy a gui elemeket könnyedén össze tudjuk állítani
  • Legyen hozzá teljeskörü debugger, hogy a hibákat minél előbb megtaláljuk
  • Legyen hozzá remote debugger
  • Legyen jól dokumentálva
  • Legyen a lehető legnagyobb a tudástára, hogy minél kevesebb dolgot kelljen nekünk lefejleszteni
  • Legyen hozzá egy jó IDE, hogy a munkát könnyítse és ne lassítsa
  • A fordító legyen minél gyorsabb
  • Az elkészült program beüzemelése legyen minél könnyebb
  • A futásbeli hibakezelés legyen minél fejlettebb
  • Könnyedén lehessen alkalmazni hozzá a fontosabb librarykat (adatbázis, 3D, script, stb)
  • Képes legyen DLL ek betöltésére (akár ctype al)
  • A könnyed fejlesztés mellett legyen gyors és ne egyen sok memóriát
  • Legyen hozzá minél több gui elem
  • Képes legyen ActiveX es objektumok használatára

ez eléggé sok feltétel, de nem árt, ha olyan nyelvet keresünk, ami mindennek megfelel. Persze, van ilyen nyelv, mert az alapján szedtem össze, hogy mit kell tudjon, azaz csaltam egy kicsit. Mivel a választott operációs rendszerünk a Windows ezért szinte csak egy nyelv jöhet szóba, de azért nézzünk meg pár lehetséges választást, megpróbálom megindokolni, hogy melyik nyelv miért nem jó.

  • C++ – mondhatni ősi nyelv, minden tud, gyors, kevés memóriát eszik, viszont kevés UI Framework van hozzá és azokhoz sincs grafikus designer. (Gtk, Gtkmm – itt megjegyezném, hogy egy magyar, Érdi Gergő eléggé kiemelt helyen van ebben a projectben, Qt – ami egy eléggé életképes alternatíva, multi platform és kifejezetten sokat tud, habár a KDE ezt a keret rendszert használja és sajnos eléggé bugos, ami nem biztos, hogy a Qt hibája, MFC – ezzel kb a kisgyerekeket kell riogatni). Maga a C++ eléggé bonyolult nyelv és nagyon sok minden nehezen implementálható, illetve saját magunknak kell megteremteni. Nincs benne védelem sok alaphiba ellen (mivel nincs saját VM je), így a memória felülírás, rossz pointer használat, mind komoly veszélyt rejt.
  • Java – a C++ al ellentétben a Javanak van saját VM je, nincs benne pointer, így memória hibákat Önmaga kivédi. A garbage collector miatt nem kell foglalkozni az objektumok menedzselésével sem. A Javahoz óriási mennyiségű library van, nem tudok olyat mondani, ami nincs megírva hozzá. Ezzel ellentétben sajnos nincs hozzá olyan editor ami valóban könnyeddé tenné a fejlesztést. Mind az Eclipse, mind a Netbeans komoly hibákkal küszködik, sem a debuggerek sem maga az IDE nem könnyen használható, nem felhasználó barát, viszont nagyon lassúak, ezáltal lassítják a fejlesztést. Mindemellett Windows alatt Java-t futtatni eléggé körülményes tud lenni, főleg a 64 bites rendszeren. UI Frameworkben nem szenved hiányt, viszont grafikus designerben annál inkább. A Netbeans és az Eclipse az elsődleges amit említenek. Az Eclipseet próbáltam, de nem működött, a weboldalán írták is, hogy jelenleg nem működik. A Netbeans pedig lassú volt, nehezen kezelhető és itt ott komoly bugokkal szenvedett.
  • Adobe Air – újszerű ötlet, de szinte azonnal ki is lehet zárni. Nem támogat multi threadinget, nem tud natív kódot hívni, nem tud dll-t betölteni, nincsenek hozzá adatbázis driverek, csak magas szintű hálózat kezelés van benne. Úgyhogy kilőve.
  • C# – igen, ez az a nyelv, amire gondoltam. Nem meglepően, mert a fentebb felsorolt elemekből minden igaz rá. Akárcsak a Javaban itt is minden meg van írva, viszont van hozzá egy nagyon komoly IDE, grafikai designer, debugger. Mivel az IDE fizetős, ezért nincsenek benne fél megoldások, nem kontárkodik bele mindenki, amitől rengeteget tud, de semmit sem jól. Képes dll ek betöltésére, képes natív kód hívására, ActiveX objektumok betöltésére, nagyon egyszerű. Mindezek mellett komoly OS supportot is kap, ami csak hab a tortán. Mindezek mellett eléri a teljes DirectX et, akár natív módon, akár a managed kódot, vagy akár az XNA-n keresztül, ami ugye multiplatformos 3D megjelenítést és még sok minden mást tesz lehetővé.

Nekem mindig egyszerű a választás és mindig a C# mellett teszem le a voksom. Egy régi munkatársam, akitől eléggé sokat tanultam, mindig azt mondta:

C# ban ha valami több mint 1 sor, akkor ott elrontottam valamit

 

Igaza is volt. Minden meg van valósítva, amire szükség lehet, és egyre több és több a már amúgy is hatalmas mennyiségű C# Managed Library, amit felhasználhatunk a toolunkhoz.

Mára ennyi, még folytatom, ahol már inkább programozás lesz, mint szövegelés.

Scriptnyelvek a gyakorlatban …

Rettentően elmés címem mellett valójában azt szeretném ma leírni, hogy hogyan is kell beépíteni a Pythont illetve a Lua-t saját C++ projectünkbe. Ha eddig nem is lett volna egyértelmű, most már biztos, hogy ez megint egy technikai írás lesz, amit remélem sikerül legalább annyira érdekesre, vagy viccesre megírnom, hogy azért ne aludjon el mindenki a monitor mögött.

LUA

Kezdjük először a Lua val, mivel ez a script áll hozzám közelebb és sokkal jobban is ismerem mint a Pythont, így talán a későbbi botlásokat könnyebben elnézik.

A Lua jelenleg az 5.1 es verziónál tart, C ben fejlesztik és egy eléggé egyedi (már a scriptnyelvek terén) nézőpontot támogat, méghozzá a stack kezelést. Gyanítom ebben rejlik hihetetlen sebességének kulcsa.

A sebességnek viszont mindig ára van, jelen esetben a kicsit sem egyszerű C interface, amivel sajnos meg kell birkóznunk.

#include <stdio.h> 
#include <stdlib.h> 
#ifdef __cplusplus
   extern "C" {
#endif // __cplusplus 

 #include <lua.h> 

#ifdef __cplusplus
    };
#endif // __cplusplus 

int main( int argc, char** argv )
{

    // Lua VM letrehozasa   
    lua_State* State = lua_open();

    // Lua alap libek betoltese 
    lua_openlibs( State );

    // myfile.lua fajl futtatase 
    lua_dofile( "myfile.lua" );

    // MyFunc fuggveny elovetele
    lua_getfield( State, LUA_GLOBALSINDEX, "MyFunc" ); 

    // elso parameter 
    lua_pushinteger( State, 10 );

    // a MyFunc fuggveny felhivasa 1 parameterrel, 0 result al 
    lua_call( State, 1, 0 ); 

    // Lua State lezarasa 
    lua_close( State ); 

    return 0;
}

Mint a kódon láthatjuk egy egyszerű függvényhívás is már egy összetettebb probléma, hát még az objektum orientált függvények, tömb, table változók.

Szerencsére vannak megoldások arra, hogy a Lua is elfogadható és könnyen használható legyen. Mivel a projectekben fontos az, hogy ne hetek, hónapok menjenek el egy egy feladattal, hanem minél rövidebb idő alatt álljon elő olyan eredmény, ami kielégítő működést ad, ezért nem mindegy, hogy mennyire nehézkes a választott scriptnyelv importálása az alkalmazásunkba.

A Lua általam legkedveltebb C++ interface-e a LuaPlus. (Dokumentáció: http://wwhiz.com/LuaPlus/LuaPlus.html)

A LuaPlus al sokkal könnyedebbé válik a Lua használata, úgy, hogy nem vesztünk jelentős sebességet.

#include <stdio.h> 
#include <stdlib.h> 
#include "LuaPlus.h" 

using namespace LuaPlus;

int main( int arcg, char** argv )
{
    // Lua VM letrehozasa es alap libek betoltese 
    LuaState* State = LuaState::Create( true );

    // myfile.lua fajl futtatasa 
     State->DoFile( "myfile.lua" );

    // MyFunc fuggveny elovetele 
    LuaFunction<> MyFuncObject = State->GetGlobal( "MyFunc" );

    // a MyFunc fuggveny felhivasa 1 parameterrel, 0 result al 
    MyFuncObject( 10 );

    // Lua State lezarasa 
    LuaState::Destroy( State );

    return 0;
}

Remélem a példával jól látható, hogy a LuaPlus egy sokkal egyszerűbb, érthetőbb interface, mint az alap C Api. Rengeteg olyan lehetőséget biztosít, amivel komoly Lua bindet építhetünk össze úgy, hogy nem megy el rengeteg felesleges idő a fejlesztésre.

Ami még nagyon hasznos a LuaPlus ban, az az, hogy Nativ és Managed kódhoz is van DLL je, így amit C++ alkalmazásunkban használunk, majdnem ugyan úgy használhatjuk C# toolunk ban is! Ez megint csak csökkenti a fejlesztési időt.

Persze ez is olyan, mint sok más interface, az ember szeret belőle sajátot írni, ami jól is hangzik elsőre, de amint próbálunk a Lua C Api mélyére ásni, rá kell jöjjünk, hogy nem olyan egyszerű feladat.

 

Python

A Python jelentősen más fajta mint a Lua. Személyes véleményem szerint itt sokkal inkább a használhatóság, beépíthetőség volt a fő szempont, mintsem a gyorsaság. Ez egyben előnye, de hátránya is a scriptnek. Előny, a már említett fejlesztési idő miatt és hátrány, a sebesség vesztés miatt. A  Python körülbelül 2 szer lassabb és 1.5 ször több memóriát eszik meg mint a Lua, habár vannak esetek, amikor Ő teljesít jobban. Jelenleg ugye a Pythonból úgymond 2 verzió létezik a 2.7 és a 3.2. Az itt megadott mérési eredmények a 3 as Pythonra vonatkoznak: http://shootout.alioth.debian.org/u32/benchmark.php?test=all&lang=python3&lang2=lua (az oldal nehézkesen működik, úgyhogy néha sokat kell próbálkozni)

De akkor lássuk hogy is építsük be a Pythont a kódunkba:

#include <Python.h> 

 int main( int argc, char** argv )
{
    // Python VM letrehozasa 
    Py_Initialize();

    // hozzaadas a path hoz az aktualis konyvtarat
    // erdemes azt, ahol a python fajljaink vannak 
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append(\".\")");

    PyObject* ModName = PyString_FromString( "my_file" );

    // a sajat fajlunk betoltese mint modul 
    PyObject* Module = PyImport_Import( ModName );
    if ( Module == NULL )
    {
        // ha nem sikerult volna, akkor error es kilepes 
        PyErr_Print();
        return 1;
    }

    PyObject* Dict = PyModule_GetDict( Module );
    // a fuggvenyunk lekerese 
    PyObject* Func = PyDict_GetItemString( Dict, "MyFunction" );

    // ellenorzes, hogy felhivhato-e 
     if ( PyCallable_Check( Func ) )
    {
        // parameter eloallitasa 
        PyObject* Args = Py_BuildValue( "(i)", 14 );
        // a fuggvenyunk felhivasa 
        PyObject_CallObject( Func, Args );
    }
    else {
        // hiba eseten hiba kiirasa es kilepes 
        PyErr_Print();
        return 1;
    }

    Py_XDECREF( Module );
    Py_DECREF( ModName );

    // VM torlese 
    Py_Finalize();
    return 0;
}

Hát elsőre úgy tűnhet, hogy jóval hosszabb kód állt elő, de ennek egy része a fájlunk és annak függvényeinek betöltése, plusz itt van hiba kezelés is, ami jóval egyszerűbb, mint a Lua ban.

A kódból nekem az jött le, hogy sokkal “felhasználó” barátabb, könnyebb a kezelése, pointerekkel dolgozik, így talán kevesebb objektum létrehozás van, mint a LuaPlus ban.

Konklúzió

Mindazok mellett, hogy milyen komoly szavakat ismerek, azt kell mondjam, hogy mindkét script nyelv nagyon érdekes, a használatuk izgalmas és rengeteg sok lehetőség rejlik bennük. Igazából nem is nagyon tudnék dönteni, hogy most melyik tetszik jobban.

Mindkét nyelvhez található IDE és Debugger is szép számmal:

LUA

  • IntellijIDEA– egy multi platform editor, eléggé sok feature el, LUA supportal, ahol saját(!) libeket lehet hozzá adni (van hozzá WoW és Rift Library). Ez nagyon hasznos tud lenni, mert így a saját projectunk függvényeit könnyedén be tudjuk illeszteni, ami segíti a fejlesztést
  • LuaEdit– teljesen ingyenes és eléggé igényes editor és debugger a Luahoz, solution fájlokat kezel
  • Scite– eléggé komoly, multi platform editor minden féle nyelvhez
  • Addon Studio– érdemes megnézni, mit is lehet csinálni egy IDE vel és egy jó script engine el. Persze ez csak egy editor, de itt látható, hogy a WoW Addon / GUI rendszere mennyire flexibilis
  • LuaStudio– egy eléggé komoly és letisztult Lua IDE és debugger
  • Decoda – sajnos fizetős, de természetesen a legjobb IDE és debugger. Előző írásomban már áradoztam róla. Mindent tud, amit kell, főleg a remote debug a kielégítő benne!

Python

  • GDB– most lehet páran megrökönyödnek, hogy hogy lehetséges, hogy a gdb-t valaha is szóba hozom, de jelenlegi munkám során találkozom vele és nem árt, ha a debugger rendszerekből nincs tíz féle. Persze egy designer vagy tester nem biztos, hogy valaha megbirkózna vele, de ettől még egy lehetőség
  • WinPdb– egy multi platformos Python debugger, persze Python ban. Ingyenes, és egyszerűen mindent tud, amit egy debuggernek tudnia kell, mindamellett, hogy még igényes is! Természetesen alapból remote debugging, úgyhogy nagyvalószínűséggel a szerveren futó Python scripteket is gond nélkül debuggolja
  • PyScripter– open source IDE és debugger, meglepően jó syntax highlight al és code completition el!
  • WingWare – fizetős program nem is próbáltam ki, a WinPDB és PyScripter után azt hiszem teljesen felesleges

 

Mindent összevetve mindkét nyelvhez vannak iDE-k és Debuggerek is, mindkettőhöz jól használhatóak a programok, viszont a Pythonnak 1 nagy előnye van: GDB, hisz nem árt, ha az ember egyből tudja debuggolni, ha akár a saját alkalmazásában hal el a script.

Nekem személy szerint az újdonság erejével tetszik a Python nagyon és abban a szerencsés helyzetben vagyok, hogy szórakozhatok mindkettővel, de hogy melyiket használnám egy projectben … az még számomra is kérdéses, ehhez azért sokkal több konkrétum kell és egy jól megfontolt döntés!

 

Addig is remélem van akinek felkeltettem az érdeklődését az alábbi 2 irományommal és kedvet csináltam némi scriptelgetéshez! Jó szórakozást mindenkinek!

Python, Lua azaz scriptnyelvek

Végre egy technikai téma, mondhatnánk, remélem értelmes lesz mindaz amit itt majd le szeretnék írni.

A mai játékok szép számmal tartalmaznak script nyelveket. Leginkább a Lua vagy a Python nyelv lehet ismerős. Egyre ritkábbak a saját script próbálkozások (mint például az Unreal Script).

Jelen írásomban a Luaról és a Pythonról fogok írni, megpróbálom összeszedni, miért is jó dolog a script, melyik mennyire jó, hol használják és mire.

Sokkal több játékban vannak scriptek, mint hinnénk, hisz a mai modern nyelvek már eléggé jól beépíthetőek, elrejthetőek, viszont vannak olyan esetek is, amikor a script a játék szerves része, a fejlesztők bátorítják a játékosokat, hogy minél több úgynevezett addont írjanak, ezzel is bővítve a játék tudását.

De akkor lássuk is, hogy mi mindenre lehet jó egy scriptnyelv egy program fejlesztésekor, főleg játék fejlesztésekor. Ha valaki ki tudja egészíteni, azt szívesen veszem.

    • Prototipizálás –  egy nagyon jó megoldás, ha a designerek a játék motorját használva egy scriptnyelvel próbálgatják a lehetőségeket. Minél komolyabb Script Engine van a játékunkban, annál több problémát tudnak megoldani, annál jobb utat találnak arra, hogy a játék sikeres legyen. Mivel egy engine-t nem csak egy játékhoz fejlesztünk (többnyire), ha nem is az első, de a második játéknál már egészen biztos behozza a ráfordított időt.
    • Design kódok – egy játék fejlesztése során sok olyan kód készül, amik a designerek kénye kedve szerint változik. Leginkább a skillek, AI vagy épp a GUI ilyen. Ezek legtöbbször olyan feladatok, amik tipikusan egy scriptnyelvnek valók. A játék engine ad bizonyos lehetőségeket a skill, AI vagy GUI rendszernek, ha ezeket a lehetőségeket kivezetjük egy scriptnek, akkor a designerek, vagy GUI fejlesztők átírhatják, alakíthatják ezeket a működéseket. Ez is egyfajta prototipizálás, de kevesebb fejlesztés, viszont általában csak egy projectet szolgál ki.
    • Triggerezés – a trigger rendszer egyik napjainkban elterjedt módja, hogy script nyelveket használnak. Ez komoly szabadságot ad a designereknek arra, hogy a pályákat egyedire építsék. Először egy RTS játékban találkoztam ezzel a megoldással (Codename Panzers: Cold War), ahol nagyszerűen működött. Persze ahol a sebesség mindennél fontosabb, ott erősen meggondolandó, hogy szükséges-e ez a megoldás.
    • GUI rendszer – egyre több helyről hallom, hogy egy játék, vagy program teljes GUI rendszere, logikája, scriptnyelvre épül. Talán a legismertebb példa maga a World of Warcraft, ahol az addonokkal tetszőleges ablakokat, widget elemeket hozhatunk létre és vezérelhetjük Őket, sőt, ha azt akarjuk a Blizzard által biztosított alap GUI interface-t 100%-ig átalakíthatjuk a saját kedvünkre. Ez a megoldás óriási flexibitást ad a GUI tervezésekor, fejlesztésekor, arról nem is beszélve, hogy a játék élettartalmát is képes meghosszabbítani, hisz a játékosok vagy fejleszthetik, vagy a mások által fejlesztett módosításokkal szinte egy új játékot játszhatnak.
      Blizzard FrameXML: WoWWiki –n találhatunk némi leírást arról, hogy mit is alkotott meg a Blizzard. A FrameXML hez hasonlóan ezt a megoldást már több MMORPG nél láttam. Jó megoldás, persze nem kevés fejlesztést igényel, de cserébe egy olyan GUI Rendszert kapunk, amivel bármit meg lehet valósítani és ha ésszel készítjük el, akkor jó sok projectet kiszolgálhat.  (ActionBarFrame)
    • Addonok – na igen, ez egy nagyon általános megfogalmazás, de sajnos nem találtam jobbat. Az addon rendszer nagyon hasznos lehet egy játékban, akár évekkel is elnyújthatja a játék élet ciklusát, jelentős terheket vehet le a fejlesztők válláról, illetve jelentősen csökkentheti a fejlesztési időt. Másik járulék az olyan addon oldalak, ahonnan a játékunkhoz tartozó addonokat letölthetik a játékosok. a Curse oldalán 4.500 WoW addon található, az egyszerű “Hello világtól”, egésszen a Grid nevű addonig, ami eléggé sok mindent tud (és nem mellesleg 4.5 millió (!) letöltése van, ami már egy teljes játéknak is jó lenne, nem hogy egy addonnak).
    • Content – vagy akár Quest rendszer is lehetne. A Pirates of the Carribean nevű játékban volt megoldva az egész játék menet scriptelve. Itt egy C syntax script nyelv volt, amit indításkor fordított le a játék. Rengeteg kiegészítő készült a játékhoz, amivel jelentősen megváltoztatták a játék menetet!

A megvalósítások után nézzük meg, hogy milyen scriptnyelvek vannak, azaz inkább azt, hogy én miket ismerek, amik szóba is jöhetnek.

Arra, hogy mi mindenre képesek ezek a nyelvek nem nagyon akarok kitérni, hisz mindegyik képes C függvények felhívására, ami mindenre képessé teszi Őket. Habár itt felmerül egy bónusz kérdés, mégpedig a C#. Persze nem megoldhatatlan feladat, ha már tudnak native C kódot hívni, hogy C# ot is elérjék, illetve a C# elérje a scriptet, de azért a legjobb az, ha ez a rész már meg van valósítva és mindezt valakik mások valósították meg. (Igen ezen a területen kicsit ellentmondásos vagyok Önmagammal, hisz mindig azt mondom, hogy mindent nekünk kell megvalósítani, akkor lesz kellően optimális, de egy komoly script nyelv elkészítése, a hozzá tartozó összes toolal … nem kicsi feladat.)

Vágjunk hát bele.

 

Számomra legjobban ismert scriptnyelv, a Lua.

A Luanak rengeteg előnye van és alig akad olyan hátránya, amit komolyan fel lehetne neki vetni.

  • Sebesség – rettentően gyors, gyorsabb mint minden más script nyelv, amivel eddig találkoztam. Az alap rendszer nagyon letisztult, nem zsúfolják tele minden féle extra felesleges feature el, ezt meghagyják a betölthető moduloknak
  • Memória használat – ugyancsak verhetetlen, pont azért, amiért gyors is. Az alap rendszer nem sok mindent csinál és amennyire lehet agyon van optimalizálva
  • Binary format – nem is tudom mennyire lehet felhozni ezt előnynek, hisz ma már alapvető elvárás ezeknél a scripteknél
  • User friendly – a Lua nagyon egyszerű, jól átlátható, a számomra általános nyelvekre hasonlít (C, JavaScript, PHP, Java) emiatt könnyen tanulható, de mégsem típusos, nincs benne pointer, vagy bonyolult alacsony szintű adatkezelés, emiatt nem csak programozóknak, hanem designereknek, grafikusoknak, tesztereknek is könnyen emészthető
  • Toolok – a Lua hoz nagyon komoly editorok és debuggerek találhatóak a neten. Egyik személyes kedvencem a Decoda, ami egy eléggé komoly IDE / Dedugger. A Lua VM hez kapcsolódik, így nem kell nekik külön kód support. (Lua Tools)
  • SDK – ez kicsit kétélű a Lua val kapcsolatban. Maga a Lua source szinte teljesen átláthatatlan, viszont készül sok féle C/C++ implementáció (és C#, Java, sőt, még Python is), amik sokkal jobban használhatóak. Személyes kedvenc ismét a LuaPlus. Sajnos binary kiadásban 2010 es a legfrissebb, amit nem is nagyon értek, mert a weboldal logjait nézegetve alig pár napos az utolsó commit. Reméljük az érkező 5.2 es Lua nál frissítik a binárisokat is. (főleg mert a source hoz Jam al kell legenerálni a legeneráló fájlokat, nem is értem hogy jut eszébe ilyesmi bárkinek is …)
  • Multi platform– igen igen! A Lua multi platform. Főleg, hogy a Lua5.1 source-a annyira ansi, hogy szinte bármire le lehet fordítani, de jó sok előre fordított bináris is van. (LuaPlus is fordul Linux alá, némi módosítással). Linux/Windows/Xbox/PS/Kb minden okos telefon
  • Modulok – azt írtam, hogy az alap rendszerbe nem akartak sok mindent zsúfolni, viszont a modularitása komoly figyelmet kapott. Rengeteg modul készült a Luahoz, amivel már szinte bármit meg tudunk csinálni (OpenGL,D3, hálózat, Windows Forms, GTK+, adatbázisok és még sorolhatnám). Saját magunk is nagyon egyszerűen fejleszthetünk hozzá saját modul-t ami így könnyebben portolható a projectek között.

Egyszóval a Lua rendkívül sokoldalú és jól használható script nyelv, rengeteg olyan tulajdonsággal, ami egy játékfejlesztés, vagy bármilyen más fejlesztés során fontos lehet. Az alap C Lua5.1 es kód (most ez a legújabb, de már készül az 5.2, ami most beta), nehezen érthető, nehezen átlátható, nem a legkönnyedebb, de vannak hozzá olyan C++ kiegészítők, wrapperek, amik könnyűvé teszik a használatát, de nem lassítják jelentősen.

Játékok amik biztos, hogy Lua-t használnak:

  • Codename Panzers: Cold War
  • World of Warcraft
  • Warhammer Online
  • Alods Online
  • Rift
  • Sim City 4
  • CryEngine
  • Supreme Commander
  • Heroes of Might and Magic V
  • S.T.A.L.K.E.R.
  • Homeworld 2
  • Maffia II

és még sokan mások:

http://lua-users.org/wiki/LuaUses
http://en.wikipedia.org/wiki/Lua_%28programming_language%29

Nem kevés alkalmazás is a Lua-t választotta, és ami még számomra is meglepő volt, hogy az Apache nak is van Lua module ja és a MySql nek is!

 

Egy másik lehetőség a Python.

A Python egy nagyon komoly script nyelv, amit leginkább Linux alatt láttam rendszer szintű scriptek írására. Rengeteget tud, viszont számomra érthetetlen módon mintha próbálna minél jobban különbözni minden más programozási nyelvtől. Ettől függetlenül sok játékban, alkalmazásban használják, nem kevés előnye miatt.

  • Fejlesztés – a Python open source, alapvetően Linuxos technológia, ettől fogva komoly fejlesztés van mögötte. Jelenleg a 3.2.1 es verzió a legújabb ami éppen egy nagy átmenet és a rendszerek még a 2.7 es verzióval jönnek. (pl a Linuxban is 2.7 es van még, illetve a kikerülő alkalmazások is inkább ezt javasolják)
  • Modulok – hát nem kevés kiegészítő található a Pythonhoz sem. Mivel a C összekötése nagyon egyszerű, mindenki mindent megírt alá, amit nagyon könnyen lehet implementálni. A Linux is nagyon sokat dobott ezen a dolgon, mivel megadta neki azt a keretet ami a Luatól nagyon hiányzik.
  • SDK – még nem nagyon néztem szét, hogy milyen C/C++ interface ek vannak a Python hoz, de az alap, amit a Python-dev csomaggal adnak (vagy ami a Python Windowsos source ban van ugye), az annyira borzalmasan egyszerű, hogy az egész dokumentációja 1 oldalt tesz ki. Emelett az IronPython egy eléggé komolyn .NET es implementációnak tűnik, ami ugyancsak komoly fejlesztési figyelmet kap.
  • Típusok – ez nem lenne alapvetően előny, de ahogy a Python oldotta meg, úgy mégiscsak az. Alapvetően a Python nem típusos nyelv, azaz nem kell megadni hogy valami string vagy int, de ettől függetlenül lehetőség van a típusok használatára, amihez a Python eléggé sok eszközt ad
  • Objektum orientáltság – a Python eléggé komolyan képes objektum orientált lenni, még öröklődés és többszörös öröklődés is van benne: Az OOP nem egy erőltetett valami típusra ráhúzott “hack” mint a Lua ban, hanem valódi, jól megvalósított eszköz, exceptionökkel, öröklődéssel, private változókkal, konstruktorral
  • Multi platform – a Python is rengeteg platformon elérhető. Linux/Windows/Xbox/PS (IronPythonról szólnak Xbox os cikkek). Linux az biztosan működő képes, mivel erősen Linux párti a Python. Maga a Python interpretert láttam már futni Windowson, de még nem néztem meg, hogy mennyire nehézkes saját Python libet csinálni Windows környezetben. A többi platformról még kevesebbet tudok

Hát igen, mindenképpen azt kell mondjam, bármennyire is Lua párti vagyok, hogy a Python egy nagyon nagyon csábító alternatíva. Viszont ameddig a sebesség döntő tényező (már a program futási sebessége), addig a Python komoly hátrányban van, mivel lassabb mint a Lua.

Pár általam ismert játék, ami Pythont használ:

  • EVE Online
  • Battlefield 2 és 2142
  • Civ IV
  • Battlefield Heroes

Mondhatnám, hogy és még sokan mások, de nem nagyon találtam említésre méltó neveket, akik Pythont használnának. Viszont a Python is rengeteg Game Engine kiegészítést tartalmaz! A PyGame egy külön game engine, ami Pythont használ!

 

Némi extra információért az alábbi linkeket tudom még javasolni:

http://lua-users.org/wiki/LuaVersusPython

http://web.archive.org/web/20040916084425/http://www.bagley.org/~doug/shootout/craps.shtml

http://lua-users.org/wiki/LuaComparison

http://en.wikipedia.org/wiki/Comparison_of_programming_languages

http://www.mvps.org/scripting/languages/