All english written articles have English article tag and lays under horizontal line. First part is written in Czech.

neděle 22. července 2012

XNA a Serializace IV. / XNA and serialization IV.

Původně jsem myslel, že dnes ukáži kus kodu pro provádění samotného ukládání a načítání, ale když jsem odeslal a vypustil do světe poslední článek, došlo mi pak v posteli (mimo jiné nejlepší místo na přemýšlení), že jsem zapomněl napsat jak je to u dědičnosti. Pokusím se to dnes napraviti.
Stejně jako minule uděláme si ukázkové dvě třídy, kdy bude jedna odvozena od druhé.
public class A{
    public A(){
    
    }

    public A(GenericReader reader){
        int version=reader.ReadInt();
        //nacitani hodnot
    }
    
    public virtual void Serialize(GenericWriter writer){
        writer.Write(0);//verze
        //zapis ostatnich hodnot
    }
}

public class B:A{
    public B(){
    
    }

    public B(GenericReader reader):base(reader){
        int version=reader.ReadInt();
        //nacitani hodnot
    }

    public override void Serialize(GenericWriter writer){
        base.Serialize(writer);
        writer.Write(0);//verze
        //zapis ostatnich hodnot
    }
} 

Následující příklad jasně ukazuje co je potřeba provést. Při ukládání metodou Serialize, je nutné pokaždé zavolat metodu od předka. Dále je také důležité aby metoda přepisovala metodu původní, to ale jak jistě milý čtenář ví provedeme klíčovými slovy virtual a override. Načítání hodnot v konstruktorech je běžné. Pouze opět zavoláme konstruktor předka pomocí base. Důležité je vědět, že se nejprve zavolá konstruktor předka a teprve potom konstruktor současný. Proto se nejprve při zápisu volá metoda od předka. Pořadí hraje v binární serializaci důležitou roli!!
Tak a zítra se podíváme konečně na obslužný kod, který napsané metody využije a zavolá.



In today's article I will write about serialization of derived classes. I simply forgot write about them yesterday.
This is our example, let's have two classes A and B:
public class A{
    public A(){
    
    }

    public A(GenericReader reader){
        int version=reader.ReadInt();
        //reading values
    }
    
    public virtual void Serialize(GenericWriter writer){
        writer.Write(0);//version
        //writing values
    }
}

public class B:A{
    public B(){
    
    }

    public B(GenericReader reader):base(reader){
        int version=reader.ReadInt();
        //reading values
    }

    public override void Serialize(GenericWriter writer){
        base.Serialize(writer);
        writer.Write(0);//version
        //writing values
    }
} 

For saving values in Serialize method is important have this method virtual. This is done by keyword virtual and override as usual. Derived class MUST call method from base class and after that write all values. This must be done because of constructors. Constructors are invoked from the most base class (in C# object class) to top most. In binary serialization is order very important.
 See you tomorrow.

sobota 21. července 2012

XNA a Serializace III. / XNA and serialization III.

Včera jsem napsal o třídách, které se postarají o ukládání dat do souborů samotných a dnes se podíváme na to, jak je využít.
Každá třída, která má být serializována na to musí být uzpůsobena. Možností je několik. Já jsem nakonec vybral možnost tuto. Každá třída má virtuální metodu Serialize, ta se stará o ukládání a speciální konstruktor, ten se stará o načítání. RunUO to má kapku jinak, ale tato možnost se mi zdála být lepší. Kupříkladu takto:
using System;

namespace VodacekEngine {
    public class Priklad {
        public Priklad(){
        }

        public Priklad(GenericReader reader){
            int version=reader.ReadInt();
            //zde nacitani promennych
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(0);//verze, k cemu se pouziva vice dale
        }
    }
}

Verze je pro všechny objekty stejná, vřele doporučuji ji tam mít. Slouží k tomu, když bude potřeba nějakou proměnnou přidat, nebo odebrat. Pokud by tam nebyla nastal by při načítání problém a obsah se načetl špatně, nebo by skončil chybou. Nejčastěji pokus o čtení za koncem souboru.
Pokud chceme přidat novou proměnnou jednoduše zvedneme proměnnou s verzí a do čtení pak na verzi dáme podmínku kupříkladu přidáme naší třídě proměnnou jméno:
using System;

namespace VodacekEngine {
    public class Priklad {
        public string Jmeno;
        public Priklad(){
        }

        public Priklad(GenericReader reader){
            int version=reader.ReadInt();
            if(version>=1)Jmeno=reader.ReadString();
            else Jmeno=null;//nebo jina pocatecni hodnota
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(1);//<------ zvednuta o 1
            writer.Write(Jmeno);
        }
    }
}

Místo podmínky, lze užít třeba switch a case, důležité je zvednout verzi a číst a zapisovat ve stejném pořadí.
Pokud by bylo potřeba proměnnou Jmeno nopak zrušit, tak opět zvedneme verzi o jedničku. Zapisování proměnné smažeme a upravíme načítání:
using System;

namespace VodacekEngine {
    public class Priklad {

        public Priklad(){
        }

        public Priklad(GenericReader reader){
            int version=reader.ReadInt();
            if(version==1)reader.ReadString();//promennou nacteme, ale neukladame
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(2);//<------ zvednuta o 1
        }
    }
}
Proměnnou načteme, ale nikam ji neuložíme. (ono taky mezi námi není kam ukládat když ta proměnná neexistuje) Načítání je potřeba provést tak jako tak, jinak hodnoty uložené za touto by byly načteny špatně. Podmínka se také mírně změnila. Tentokráte testujeme pouze na tu jednu verzi ve které se proměnná vyskytovala.
Dalším problematickým případem je ukládání polí. Používá se tam jeden fígl. Pojďme se podívat na naši ukázkovou třídu:
using System;

namespace VodacekEngine {
    public class Priklad {
        public string[] Jmena;
        public Priklad(){
            Jmena=new string[]{"Pepa","Jernom","Kucir","Jelen"};
        }

        public Priklad(GenericReader reader){
            int version=reader.ReadInt();
            if(version>=1){
                int pocet=reader.ReadInt();//nacteme pocet prvku
                Jmena=new string[pocet];//vytvorime pole o poctu prvku
                for(int i=0;i>pocet;i++){
                    Jmena[i]=reader.ReadString();//postupne nacteme prvky pole
                }
            }
            else Jmena=new string[]{"Pepa","Jernom","Kucir","Jelen"};//initializace
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(1);//<------ zvednuta o 1
            writer.Write(Jmena.Length);//zapiseme pocet prvku
            for(int i=0;i<Jmena.Length;i++){
                writer.Write(Jmena[i]);//zapisuje vsechny prvky
            }
        }
    }
}
Nejprve se zapíše počet prvků pole a poté prvky samotné. Při načítání se načte počet a for cyklem se provede načítání jednotlivých prvků.

Tak to je pro dnešek vše. Příště (snad zítra) se podíváme na kod, který řídí zápis a čtení do souborů a který výše uvedené metody volá.



In my last English article I wrote something about classes that are used to saving and loading types from files. In this article I will write something about implementation of reading and writing code in each class, that is need to be serialized.
Each class have one virtual method Serialize (this method is used to write data to file) and special constructor for reading values from file. RunUO (ultima online emulator) is using slight different idea, but I thing that this is better for our purpose. Let's have this simple example class:
using System;

namespace VodacekEngine {
    public class Example {
        public Example(){
        }

        public Example(GenericReader reader){
            int version=reader.ReadInt();
            //here reading values
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(0);//version, I will discus it later
        }
    }
}
Variable called version is very important. This variable is used when you want to add or remove some variable from serialization. If this variable don't exist, you can get into some serious trouble.
If we want to add new variable, we must increase version. Here is example with our class:
using System;

namespace VodacekEngine {
    public class Example {
        public string Name;
        public Exammple(){
        }

        public Example(GenericReader reader){
            int version=reader.ReadInt();
            if(version>=1)Name=reader.ReadString();
            else Name=null;//here belong initial value
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(1);//<------ increased
            writer.Write(Name);
        }
    }
}
If is version grater than one variable is read from file, if not we should initialize it.Writing is more simple. Only version is increased and added new variable.

If we need to remove variable Name simply again increase version and remove writing part. In reading part simply add if statement for last version where variable exist. Something like this:
using System;

namespace VodacekEngine {
    public class Example {

        public Example(){
        }

        public Example(GenericReader reader){
            int version=reader.ReadInt();
            if(version==1)reader.ReadString();//variable MUST be read, but is not stored
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(2);//<------ increased
        }
    }
}
Removed variable NEED to be read but is not stored. Variable is not exist any more.
Our last example is writing arrays. There is used little trick. Here is our example class:
using System;

namespace VodacekEngine {
    public class Example {
        public string[] Names;
        public Example(){
            Names=new string[]{"Pepa","Jernom","Kucir","Jelen"};
        }

        public Example(GenericReader reader){
            int version=reader.ReadInt();
            if(version>=1){
                int count=reader.ReadInt();//count of items in array
                Names=new string[count];//creates array
                for(int i=0;i>pocet;i++){
                    Names[i]=reader.ReadString();//reading members of array
                }
            }
            else Names=new string[]{"Pepa","Jernom","Kucir","Jelen"};//initialize
        }
 
        public virtual void Serialize(GenericWriter writer){
            writer.Write(1);//<------ increased
            writer.Write(Names.Length);//write count of members in array
            for(int i=0;i<Names.Length;i++){
                writer.Write(Names[i]);//write each member
            }
        }
    }
}
First of all is written count of members in array and then in for loop all members. Version is off course increased. In reading part, is standard if statement for adding values. Then is read count of members in array. Array is created and then in for loop added variables. Really simple.
That is all for today. In next article I will show you code that is used for calling this methods.

pátek 20. července 2012

Viděl jsem: Doba ledová 4/ Czech only article

Tak jsem po delší době zašel do kina spolu s panem bratrem a Patrikem a Káťou. Tentokráte padla volba na nový animák Doba ledová 4.
Když jsme se dostali do kina docela sem si myslel kam se to sakra vlezl. Sál byl plný malých dětí s rodiči. Naopak reklamy (bezesporu nejlepší co v kině můžete vidět) nezklamaly. Hlavně upoutávka na nový český animovaný film Kozí příběh (se sýrem? nebo jak se menuje) nás všechny pobavila. Dostali jsme i předfilm. Docela jsem se podivoval, ale nakonec se ukázalo, že bez něj by celý program na těch 130 korun, které po nás chtěli, asi neměl.
Film samotný nebyl špatný a co navíc byl okořeněn komentářem jednoho malého diváka, který seděl za námi. Takové hlášky jako ,,Kdyby se pořádně dívali, tak by se jim to nestalo!" byly prostě super!

XNA Serializace den po té / XNA Serialization the day after

Minule sem sepsal jaký máme možnosti a dneska se podíváme jak vlastně na to. Vybral jsem nakonec možnost druhou tedy, jeden soubor s indexem a druhý s daty. Samotné třídy jsem sebral (snad se to může) ze zdrojáků RunUO ze starší verze 2.0 RC1, tedy z té samé kterou používáme na ŽvB. Ti, kteří by se zajímali jak vlastně funguje (nebo jak si to představuju že to funguje) ukládání na již zmíněném RunUO snad také dostanou zajímavé informace. Třídy jsem vypreparoval a upravil tak, aby byly co nejjednodušeji a mohl je každý použít. Stáhnout si je můžete tady. Jak to vlastně celé funguje? Velmi snadno.Veškerou zapisovací parádu obstarávají dvě třídy: GenericReader a GenericWriter. Netřeba vysvětlovat co která asi bude dělat je to zřejmé již z jejich názvů. Třídy lze použít i k jiným možnostem ukládání, které jsem popsal v předešlém článku, možnosti se jen budou lišit samotným kodem, který je určen pro ukládání. Třídy obsahují ukládání základních datových typů se kterými se můžete běžně setkat. Jenže jak uložit třeba Vector2, Vector3 nebo Matrix (tedy matici) a jiné další? Také velmi snadno(jak jinak). Je možné je rozložit na prvky ze kterých jsou složeny. Vector2 na X a Y souřadnici, Vector3 na souřadnice tři, Matrix není nic jiného než 16 float čísel.

public void Write(Vector3 vector){
    if ((m_Index + 12) > m_Buffer.Length) Flush();
 
    Write(vector.X);
    Write(vector.Y);
    Write(vector.Z);
}

public Vector3 ReadVector3(){
    return new Vector3(ReadFloat(), ReadFloat(), ReadFloat());
}

Horší situace nastane pokud budeme chtít ukládat třeba třídu Model, Texture2D a další věci načítané pomocí Content Pipeline. Zde to lze snadno obejít, uložíme pouze jména souborů a ty potom běžně po proběhnutí serializace načteme. Uložit lze i obsah delegátů, ale o triku který jsem na ně vymylel napíši něco později. Jediné na co je potřeba myslet je to, že je nutné zapisovat a číst prvky ve stejném pořadí.

Jak používat tyto třídy opět v dalším článku.



In my last post about basic of serialization I wrote something about possible ways. In this post I want to introduce classes for reading and writing into binary files. I modified classes from RunUO 2.0 RC1 source, that is used as Ultima Online emulator on ŽvB project and add reading and writing code for some XNA classes. Code can be downloaded from here. Reading and writing those types is very simple. Vector2, Vector3 or Matrix are only couple of floats. Vector2 have two X and Y, Vector3 have three and Matrix is only set of 16 floats. Here is example of serialization of Vector3:

public void Write(Vector3 vector){
    if ((m_Index + 12) > m_Buffer.Length) Flush(); 
 
    Write(vector.X);
    Write(vector.Y);
    Write(vector.Z);
}

public Vector3 ReadVector3(){
    return new Vector3(ReadFloat(), ReadFloat(), ReadFloat());
}

Texture2D, Model etc.. (basically all classes that are loaded by content pipeline) classes needs to be stored as strings. Simply write only file names of that assets and then load them by Content Pipeline. Only one thing is important. All parts needs to be written and read in same order.

In next article I will show you my code that handles saving and loading.