понедельник, 21 января 2013 г.

Cocos2d-x Жрем память....


Если вы в тупике по поводу жора памяти в cocos2d-x, возможно эта статья вам поможет!

С недавнего времени начал писать под iOS, выбрал cocos2d-x. Скачал с http://www.cocos2d-x.org версию движка 2.0.4. Прочитал не мало инфы про него, прочитал англоязычную книжку cocos2d. И решил пора начинать... До недавнего времени я программировал на JAVA для OS Android. Использовал бесплатный движок AndEngine. Написал пару игрушек, первая - Охота Мюнгхаузена (первый блин комом) , вторая Truck advernture. Вот собственно: Мои игры.
Так вот, к чему все это... К тому, что вторая моя игра попала в топ на www.amazon.com. И начала приносить доход. Это и подтолкнуло меня переписать ее для iOS, чтобы опубликовать на AppStore. Все было хорошо, игрушка писалась, потихоньку, я почти закончил проект.  Пишу ее в XCode под MAC OS.  Но возникла проблема, приложение начало падать. Причем XCode ничего не говорил по этому поводу, кроме Finished Running Truck Adventure... Несколько дней прогонял ее в профайле (инструменты для разработчика, можно запустить свой проект для профайлинга), нашел пару незначительных ликов по 5 кб. В Allocations память, как будто и не девалась никуда... В Leaks, как уже говорил, нашел пару ликов... В общем продолжалось это неделю, так нечего и не сдвинулось с места. Перечитал кучу форумов,  ответа так и не нашел...
Но, буквально, вчера случайно наткнулся на один пост: http://stackoverflow.com/questions/13231570/is-it-normal-that-my-cocos2d-app-increase-real-memory-usage-every-second/13234008#13234008,  Там у человека та же проблема... Он натолкнул меня на мысль в профайле запустить Activity Monitor, до этого анализировал только память и лики...  

Вот оно!! В Activity Monitor видно, как мой процесс каждые 2 секунды отъедает по 200 кб. физической памяти!!!! непонятно куда!!!?? 

И начались пляски с бубном, закоментил всё в стартовом слое, жор так и не уменьшился.
В общем не буду долго писать, что я только не предпринимал, все в бестолку!
В архиве с движком также идут сэмплы. Очень полезные примеры... Так вот запускаю TestCpp - это демка возможностей, от авторов движка, с исходниками, в Activity Monitor память как вкопанная, ничего нигде не растет :)  Копирую свой стартовый слой в проект TestCpp,  подправил код запуска, теперь TestCpp стартует мой CCLayer... Все нормально, память не растет. Значит дело не в моем коде, а настройках проекта, я так полагаю. Несколько часов сравнивал настройки своего проекта и TestCpp, ничего из этого не вышло!
Но я решил свою проблему. Удалил все с демо проекта TestCpp и добавил туда файлы с классами и ресурсами, со своего проблемного проекта... Test Cpp переименовал в Truck Adventure вот так:http://stackoverflow.com/questions/5043066/change-name-of-iphone-app-in-xcode-4
И все в прядке.
Надеюсь вам поможет моя статья )))

среда, 2 января 2013 г.

Пример использования CCSAXParser для cocos2d-x



-->           Каждый программист, который хочет написать более-менее приличную игру, рано или поздно сталкивается с вопросом загрузки различных данных, к примеру игровых уровней.
Естественно, данный вопрос коснулся и меня. С недавнего времени я решил оставить AndEngine и перешел на Cocos2d-x, т.к. купил себе MAC BOOK PRO и скачал XCode. И глупо было-бы ограничиться написанием игр только под android. Тем более Cocos2d-x позволяет писать мультиплатформенные приложения для iOS, Android, Win32, MacOS не вдаваясь в подробности реализации API OpenGL.  Cocos2d-x это порт знаменитого движка Cocos2d. 2D-X написан на С++, соответственно и игры с применением этого движка пишутся на C++. Так как C++ был моим любимым языком со студенческих лет, то никаких сомнений не осталось! Этот движок - то что нужно для меня!

Итак, в этой статье я хочу рассказать, как использовать класс CCSAXParser. Этот класс написан нашим программистом Максимом Аксеновым и был включен в состав движка.
SAX парсинг - это последовательное чтение XML с вызовами CALLBACK ф-ций. Таким образом он является событийным парсером. События возникают каждый раз при чтении очередного элемента XML файла. Когда элемент поступает на вход парсера, он вызывает ф-цию обратного вызова OnStartElement. В своем обработчике данного события можно выполнить необходимые проверки и обычно получить массив атрибутов элемента. Когда на вход парсера поступает закрывающий тэг соотв. элемента, вызывается событие OnEndElement. В общем, об SAX  парсере написано много статей в интернете и, при желании, можно получить более подробную информацию об этом способе разбора XML.  После долгих поисков примеров применения SAX парсера в интернете, именно для Cocos2d-x, я ничего так и не нашел.  Мне повезло писать SAX парсинг ранее на JAVA, когда я писал  игру Truck adventure для android, таким образом, я уже имел опыт написания своего обработчика для SAX парсера. 
Итак начнем.
Вот как объявлены классы для парсинга в кокосе:
class CC_DLL CCSAXDelegator
{
public:
    virtual void startElement(void *ctx, const char *name, const char **atts) = 0;
    virtual void endElement(void *ctx, const char *name) = 0;
    virtual void textHandler(void *ctx, const char *s, int len) = 0;
};

class CC_DLL CCSAXParser
{
    CCSAXDelegator*    m_pDelegator;
public:
    CCSAXParser();
    ~CCSAXParser(void);

    bool init(const char *pszEncoding);
    bool parse(const char* pXMLData, unsigned int uDataLength);
    bool parse(const char *pszFile);
    void setDelegator(CCSAXDelegator* pDelegator);


    static void startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts);
    static void endElement(void *ctx, const CC_XML_CHAR *name);
    static void textHandler(void *ctx, const CC_XML_CHAR *name, int len);
};
 


Из этого объявления понятно,  что наш класс, в котором  будет происходить парсинг, должен быть унаследован от CCSAXDelegator. А переопределенные нами методы startElement, endElement и textHandler и есть необходимые обработчики событий. Т.е. когда парсер во входных данных встретит начало элемента, соответственно будет вызвана ф-ция startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts),
где name - это имя пришедшего  XML элемента, а в массиве atts - содержатся все атрибуты элемента, причем, сначала идет название атрибута, а потом его значение (key, value).
Соответственно, при закрывающем теге элемента будет вызвана ф-ция endElement

Теперь приведу примеры XML  и своего кода парсинга.


Мой XML файл имеет следующий формат:

<?xml version="1.0" encoding="utf-8"?> <landscape>
   
<level number="1-1" length="10000">
    <back backb="back_b" backf="back_f" />
    <danger name="danger_brevno1" x="5522" y="356" angle="0"
        type="revolute" x_axis="-1" y_axis="-1" speed="1" backgroup="true"
        torque="30000" motor="true" center_r="true"  w="241" h="241"/>
    <element name="danger_brevno5" x="9875" y="383" angle="0"  w="25" h="225"/>
    <element name="danger_brevno4" x="3472" y="273" angle="0"  w="269" h="25"/>
    <element name="tree1" x="7717" y="357" angle="0"  w="100" h="122"/>
    ...

</level>
...
</landscape>
 

Парсинг этого файла происходит в классе  LandLoader, который осуществляет загрузку необходимого уровня игры. Приведу пример объявления своего класса и его реализации:

class LandLoader: public CCSAXDelegator
{
public:
    LandLoader(CCLayer* _gameLayer,
               CCLayer* _backFrontLayer,
               CCLayer* _backBackLayer,
               b2World* _world,
               DangerObjectManager* _dom);
    ~LandLoader();
    void loadLevel(string namelevel);
private:
    const char* TAG_LEVEL     ="level";
    const char* TAG_NUMBER     ="number";
    const char* TAG_LENGTH    ="length";
    const char* TAG_NAME    ="name";
    const char* TAG_X         ="x";
    const char* TAG_Y         ="y";
    const char* TAG_W         ="w";
    const char* TAG_H         ="h";
    const char* TAG_ELEMENT ="element";
    const char* TAG_VULKAN     ="vulkan";
    const char* TAG_SLOWCPU ="slowcpu";
    const char* TAG_FAKE     ="fake";
    const char* TAG_ANGLE     ="angle";
    const char* TAG_BACK     ="back";
    const char* TAG_BACKB     ="backb";
    const char* TAG_BACKF     ="backf";
   
    // danger
    const char* TAG_DANGER="danger";
    const char* TAG_DANGERD="danger_dyn";
    const char* TAG_DANGERDB="danger_dyn_box";
    const char* TAG_DANGERDC="danger_dyn_circle";
    const char* TAG_TYPE = "type";
    const char* TAG_CENTERR ="center_r";
    const char* TAG_MOTOR="motor";
    const char* TAG_TORQUE="torque";
    const char* TAG_SPEED="speed";
    const char* TAG_X_ANCHOR="x_anchor";
    const char* TAG_Y_ANCHOR="y_anchor";
    const char* TAG_X_AXIS="x_axis";
    const char* TAG_Y_AXIS="y_axis";
    const char* TAG_X_LEFT="x_left";
    const char* TAG_Y_LEFT="y_left";
    const char* TAG_X_RIGHT="x_right";
    const char* TAG_Y_RIGHT="y_right";
    const char* TAG_BACKGROUP="backgroup";
    // bonus
    const char* TAG_BONUS  ="bonus";
    const char* TAG_BONUSTYPE  ="type";
    // finish
    const char* TAG_FINISH  ="finish";
    // check point
    const char* TAG_CHECKPOINT  ="checkpoint";
    // car part
    const char* TAG_CARPART="carpart";
    const char* TAG_TRUCK="truck";
    CCLayer*   gameLayer;
    CCLayer*   backBackLayer;
    CCLayer*   backFrontLayer;
    b2World*   world;
    DangerObjectManager* dangerManager;
    CCSAXParser* parser;
    bool bInLevel=false;
    void initBorder();
    //**************
   
    void createElement(CCLayer* landLayer,string name, float pX, float pY);
    void createDangerDynamic(CCLayer* landLayer,string name,float pX, float pY);
    void createBackBBackF(CCLayer* backBackLayer, CCLayer* backForeLayer);
    // parsing xml
    void startElement(void *ctx, const char *name, const char **atts);
    void endElement(void *ctx, const char *name);
    void textHandler(void *ctx, const char *s, int len);
   
    // get atts Key,Value
    const char* getValue(const char* szKey,const char** atts);
};



Здесь видно, что он унаследован от CCSAXDelegator, соответственно, мы должны имплементировать  методы о которых писалось ранее. Вот код реализации SAX парсинга (здесь приведу только необходимые для понимания парсинга методы класса). В добавление к сказанному, для понимания процесса: игровые уровни в моей игре строятся в процессе парсинга, когда парсер разбирает очередной элемент, в игре создаются соответствующие объекты и физические тела. 

В конструкторе класса создаем парсер:

LandLoader::LandLoader(CCLayer* _gamelayer,CCLayer* _backFrontLayer,
                       CCLayer* _backBackLayer,b2World* _world,DangerObjectManager* _dom)
{

...
    parser = new CCSAXParser();
    parser->setDelegator(this);
};

 
В этом методе получаем полный путь к файлу уровней и начинаем процесс парсинга.
 
void LandLoader::loadLevel(string namelevel)
{
    const char* fname = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("landlite.xml");
    bInLevel=false;
    parser->parse(fname);
}


В процессе парсинга, с помощью данного метода, получаем значение нужного атрибута из массива атрибутов.  Написан он криво и может быть зациклен. Я привожу здесь пример без оптимизации данного метода. Перепишите его для себя сами.

const char* LandLoader::getValue(const char *szKey, const char **atts)
{
    const char* res = NULL;
    if (atts!=NULL)
    {
        bool bflag=false;
        int i=0;
        while (!bflag)
        {
            if (atts[i]!=NULL)
            {
                const char* key = atts[i];
                const char* value = atts[i+1];
                i+=2;
                if (strcmp(szKey, key)==0)
                {
                    res = value;
                    bflag=true;
                }
            }
        }
    }
    return res;
}

 

Далее идут необходимые нам методы:
Этот метод устанавливает флаг bInLevel, если на вход пришел тег TAG_LEVEL с атрибутом TAG_NUMBER  = "1-1" Это частный случай, только для примера.   
TAG_LEVEL, TAG_NUMBER - это константы, объявленные  в описании класса.

void LandLoader::startElement(void *ctx, const char *name, const char **atts)
{
   
    if (strcmp(name, TAG_LEVEL)==0&&!bInLevel)
    {
       if (strcmp(getValue(TAG_NUMBER,atts),"1-1")==0)
        {
            bInLevel = true;
            int nWidth = atoi(getValue(TAG_LENGTH,atts));
            Const::me()->levelWidth = nWidth;
            initBorder();
        }
    }
    else
    // back
    if (strcmp(name, TAG_BACK)==0&&bInLevel)
    {

        const char* backB = getValue(TAG_BACKB,atts);
        const char* backf = getValue(TAG_BACKF,atts);

    }
    else
    // element
    if (strcmp(name, TAG_ELEMENT)==0&&bInLevel)
    {
        float pY=0;
        float pX=atoi(getValue(TAG_X, atts));
        const char* value = getValue(TAG_Y,atts);
        if (strcmp(value,"bottom")==0) pY = Const::me()->SCREEN_HEIGHT-100;
        else pY = atof(value);
        const char* szname = getValue(TAG_NAME,atts);
        string name(szname);
        createElement(gameLayer,name,pX,pY);
    }
    else
        // DANGER DYNAMIC
        if (
            (strcmp(name,TAG_DANGERD)==0||
             strcmp(name,TAG_DANGERDB)==0||
             strcmp(name,TAG_DANGERDC)==0)&&bInLevel
        )
        {

            float pY=0;
            float pX=atoi(getValue(TAG_X, atts));
            const char* value = getValue(TAG_Y,atts);
            if (strcmp(value,"bottom")==0) pY = Const::me()->SCREEN_HEIGHT-100;
            else pY = atof(value);
            const char* szname = getValue(TAG_NAME,atts);
            string name(szname);
            createDangerDynamic(gameLayer,name,pX,pY);
        }
}
void LandLoader::endElement(void *ctx, const char *name){
    if (strcmp(name, TAG_LEVEL)==0&&bInLevel)
    {
        dangerManager->createDDs(gameLayer);
        bInLevel=false;
    }
}


 
void LandLoader::textHandler(void *ctx, const char *s, int len)
{
    CCLog("text handler");
}


Я думаю, кому надо разберутся в данном коде. И надеюсь, что эта статья поможет Вам написать свои парсеры для своих нужд. 

С наилучшими пожеланиями )