九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

OGRE On iPhone ----Ogre的iPhone基础模版框架源代码分析

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    当想要在iPhone上使用某个3D引擎的时候,感觉我这水平,自己写好像还不现实,学到自己能写都不知道要到何年何月了,于是折腾过没有官方支持但是比较简单而且我比较熟悉的Irrlicht。虽然的确成功了(见我原来的文章

),
但是弄2D游戏的时候,(用其他引擎)经历过一些痛苦的事情后,我发现强大,成熟的引擎,以及官方的支持是多么重要。。。。。于是,虽然Ogre的效率在
iPhone上还有些难以承受,但是,我还是希望学习学习,并将Ogre作为优先使用的3D引擎。谁叫Ogre那么流行呢?将来要是弄老本行,去做网络游
戏,估计也不错^^

 

本文写作目的:

1.Ogre的基础模版程序

是Ogre在iPhone平台的模版程序的基础,但是此模版程序没有太多的说明和注释,作者仅仅是说程序具有自解释性。。。。。我这里代为简单解释一下。。。。

2.Ogre虽然是跨平台引擎,但是既然跨平台,在各平台就会有些差异存在,而从桌面程序到iPhone这样的移动平台,差异就更大了,我这里不从源码的角度,仅仅从示例程序对Ogre的使用角度指出这些差异。

而怎么在iPhone上编译运行Ogre,怎么样安装Ogre为iPhone做的项目模版,因为有官方支持嘛,下个SDK

,非常容易,不像Irrlicht那样需要调整很多东西,所以,这些都不是本文的重点,大家自己摸索一下吧。本文也不是Ogre的入门教程,这个请去看Ogre的WIKI

,也有中文版

。(但是WIKI的中级教程有些老,代码一般不能直接在新版本的Ogre和CEGU中工作,但是理解后,稍微进行点改动就行了,中文版可能更老,我没有看,推荐还是去看原版的好,毕竟都是很简单的东西)

 

基础框架了解

在弄清楚一切之前,利用Ogre的iPhone的基础框架,可以得到以下的运行效果:

一个癞蛤蟆一样的Ogre标识模型。。。。。我见过最难看的一个。。。。。。

先在机器上运行一下,了解这个基础框架实现了哪些功能,主要有下列三个:

1.3D场景中显示了天空盒和一个癞蛤蟆皮Ogre头。。。。

2.2D UI中显示了一系列的Debug信息以及OGRE的标识

3.支持触摸进行Camara的旋转。

然后,下面对此框架的了解除了大致框架外,了解3个功能是在哪里实现的。

 

基础框架代码分析

主要部分是OgreFramework.h和OgreFramework.cpp两个文件包含的OgreFramework类,这个类虽然内容简单,但是
基本功能齐全,并且,在此简单的框架的基础上,尽量简单的实现了跨平台,这样的话,可以尝试开发Mac,Win32程序,然后移植到iPhone中,以加
快开发速度,缩短开发周期。

如下所示

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MultiTouchListener

#else

class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MouseListener

#endif

OgreFramework是一个C++类,并通过Ogre::Singleton做成单件,并且,这里可以看出来,通过OIS的MultiTouchListener来支持多点触摸。

所有的接口如下:

public:

    OgreFramework();

    ~OgreFramework();

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

    bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MultiTouchListener *pMouseListener = 0);

#else

    bool initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener = 0, OIS::MouseListener *pMouseListener = 0);

#endif

    void updateOgre(double timeSinceLastFrame);

    void updateStats();

    void moveCamera();

    void getInput();

    bool isOgreToBeShutDown()const{return m_bShutDownOgre;}  

    bool keyPressed(const OIS::KeyEvent &keyEventRef);

    bool keyReleased(const OIS::KeyEvent &keyEventRef);

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

    bool touchMoved(const OIS::MultiTouchEvent &evt);

    bool touchPressed(const OIS::MultiTouchEvent &evt);

    bool touchReleased(const OIS::MultiTouchEvent &evt);

    bool touchCancelled(const OIS::MultiTouchEvent &evt);

#else

    bool mouseMoved(const OIS::MouseEvent &evt);

    bool mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

    bool mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID id);

#endif

可以从接口看出,此框架设计的使用方式是直接作为变量/成员变量使用,不是希望使用者继承此类。下面分别看各个部分。

初始化

其他部分都属于进一步的操作和更新了,主要部分是Ogre的初始化部分,在initOgre,先看看这个函数:(感觉必要的地方我添加了注释)

 

/|||||||||||||||||||||||||||||||||||||||||||||||

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MultiTouchListener *pMouseListener)

#else

bool OgreFramework::initOgre(Ogre::String wndTitle, OIS::KeyListener *pKeyListener, OIS::MouseListener *pMouseListener)

#endif

{
// 日志管理的初始化

    new Ogre::LogManager();

    

    m_pLog = Ogre::LogManager::getSingleton().createLog("OgreLogfile.log", true, true, false);

    m_pLog->setDebugOutputEnabled(true);

   
// 不是静态链接的时候使用plugins.cfg配置文件,因为iPhone只能使用静态链接方式,没戏了

    String pluginsPath;

    // only use plugins.cfg if not static

#ifndef OGRE_STATIC_LIB

    pluginsPath = m_ResourcePath + "plugins.cfg";

#endif

    
// Root的创建(Ogre::Root* m_pRoot;)

    m_pRoot = new Ogre::Root(pluginsPath, Ogre::macBundlePath() + "/ogre.cfg");

    

#ifdef OGRE_STATIC_LIB

    m_StaticPluginLoader.load();

#endif

    
// 代码虽然是显示配置对话框的代码,但是示例中不会显示配置对话框,而是直接restore了原来的配置

    if(!m_pRoot->showConfigDialog())

        return false;

   
// 渲染窗口的创建及初始化(Ogre::RenderWindow* m_pRenderWnd;)

    m_pRenderWnd = m_pRoot->initialise(true, wndTitle);

    
// iPhone平台特定操作,设定屏幕位置及大小(这些都是随着设备就固定了的),这里的(0,0)还是2维坐标,即屏幕的左上角
// 有意思的是一开始的时候,Ogre就认为iPhone设备是横的。。。。即RenderWnd height: 320     width: 480
// 即状态OR_LANDSCAPELEFT  = OR_DEGREE_270

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

  m_pRenderWnd->reposition(0, 0);

  m_pRenderWnd->resize(m_pRenderWnd->getHeight(), m_pRenderWnd->getWidth());

#endif

    
// 以下是SceneMgr,Camera,Viewport的创建及初始化,与一般的过程一样

    m_pSceneMgr = m_pRoot->createSceneManager(ST_GENERIC, "SceneManager");

    m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.7, 0.7, 0.7));

    

    m_pCamera = m_pSceneMgr->createCamera("Camera");

    m_pCamera->setPosition(Vector3(0, 60, 60));

    m_pCamera->lookAt(Vector3(0,0,0));

    m_pCamera->setNearClipDistance(1);

    

    m_pViewport = m_pRenderWnd->addViewport(m_pCamera);

    m_pViewport->setBackgroundColour(ColourValue(0.8, 0.7, 0.6, 1.0));

    

    m_pCamera->setAspectRatio(Real(m_pViewport->getActualWidth()) / Real(m_pViewport->getActualHeight()));

    

    m_pViewport->setCamera(m_pCamera);

    
// OIS的部分,构造的方式为了跨平台,所以有些独特^^通过字符串的方式来索引参数创建,
// 传递的参数是窗口的句柄,但是也转换成string了

    unsigned long hWnd = 0;

    OIS::ParamList paramList;

    m_pRenderWnd->getCustomAttribute("WINDOW", &hWnd);

    

    paramList.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(hWnd)));

    

    m_pInputMgr = OIS::InputManager::createInputSystem(paramList);

    
// OIS有MultiTouch的支持,但是在这个框架中还是直接赋值给m_pMouse(这个变量已经根据宏分别创建了)

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

    m_pKeyboard = static_cast<OIS::Keyboard*>(m_pInputMgr->createInputObject(OIS::OISKeyboard, true));

    m_pMouse = static_cast<OIS::Mouse*>(m_pInputMgr->createInputObject(OIS::OISMouse, true));

    

    m_pMouse->getMouseState().height = m_pRenderWnd->getHeight();

    m_pMouse->getMouseState().width     = m_pRenderWnd->getWidth();

#else

    m_pMouse = static_cast<OIS::MultiTouch*>(m_pInputMgr->createInputObject(OIS::OISMultiTouch, true));

#endif

    
// 这里可以参考此类的构造函数,即允许构造此类的时候,传递外来的输入响应对象。

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

    if(pKeyListener == 0)

        m_pKeyboard->setEventCallback(this);

    else

        m_pKeyboard->setEventCallback(pKeyListener);

#endif

    

    if(pMouseListener == 0)

        m_pMouse->setEventCallback(this);

    else

        m_pMouse->setEventCallback(pMouseListener);

    
// 读取配置,与一般的情况一样,只是多了个m_ResourcePath作为基础目录,为Mac和iPhone准备的。
// 这两个平台因为用了Bundle,所以与PC有些不一样

    Ogre::String secName, typeName, archName;

    Ogre::ConfigFile cf;

    cf.load(m_ResourcePath + "resources.cfg");

    

    Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();

    while (seci.hasMoreElements())

    {

        secName = seci.peekNextKey();

        Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();

        Ogre::ConfigFile::SettingsMultiMap::iterator i;

        for (i = settings->begin(); i != settings->end(); ++i)

        {

            typeName = i->first;

            archName = i->second;

// 还是为Mac和iPhone进行了一些特殊处理,英文注释很详细了

#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE || OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

            // OS X does not set the working directory relative to the app,

            // In order to make things portable on OS X we need to provide

            // the loading with it's own bundle path location

            if (!Ogre::StringUtil::startsWith(archName, "/", false)) // only adjust relative dirs

                archName = Ogre::String(Ogre::macBundlePath() + "/" + archName);

#endif

            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);

        }

    }

    Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

    Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

    
// 创建计时器

    m_pTimer = OGRE_NEW Ogre::Timer();

    m_pTimer->reset();

    // 获取Debug的Overlay层,用于输出一些调试信息

    m_pDebugOverlay = OverlayManager::getSingleton().getByName("Core/DebugOverlay");

    m_pDebugOverlay->show();

    

    m_pRenderWnd->setActive(true);

    

    return true;

}

 

这个函数的目的是很单纯的,作为框架性代码,没有像Ogre的基础教程createScene函数一样,添加场景创建的代码,仅仅是初始化了一些相关的对象。而这个函数已经完成了此基础框架希望完成的大部分功能了。

 

输入

keyPressed,mouseMoved在iPhone中是完全没有什么用了,touch的系列接口虽然与iOS SDK基本一致,但是参数上OIS进行了进一步的分装,估计是为了将来方便移植到其他触摸移动平台(比如Android)。

此参数定义如下:

    //! Touch Event type

    enum MultiTypeEventTypeID

    {

        MT_None = 0, MT_Pressed, MT_Released, MT_Moved, MT_Cancelled

    };

    class _OISExport MultiTouchState

    {

    public:

        MultiTouchState() : width(50), height(50), touchType(MT_None) {};

        /** Represents the height/width of your display area.. used if touch clipping

        or touch grabbed in case of X11 - defaults to 50.. Make sure to set this

        and change when your size changes.. */

        mutable int width, height;

        //! X Axis component

        Axis X;

        //! Y Axis Component

        Axis Y;

        //! Z Axis Component

        Axis Z;

        int touchType;

        inline bool touchIsType( MultiTypeEventTypeID touch ) const

        {

            return ((touchType & ( 1L << touch )) == 0) ? false : true;

        }

       

        //! Clear all the values

        void clear()

        {

            X.clear();

            Y.clear();

            Z.clear();

            touchType = MT_None;

        }

    };

    /** Specialised for multi-touch events */

    class _OISExport MultiTouchEvent : public EventArg

    {

    public:

        MultiTouchEvent( Object *obj, const MultiTouchState &ms ) : EventArg(obj), state(ms) {}

        virtual ~MultiTouchEvent() {}

        //! The state of the touch - including axes

        const MultiTouchState &state;

    };

 

基本上还是很容易理解的,就是有些奇怪的添加了Z坐标,难道将来会有立体触摸?-_-!OIS的作者想的还真远啊。。。。。。。

此外,这种参数封装还是有些弱的,iOS的SDK要强大一些,直接内置了多次触摸的查询等的支持,OIS为了通用,看来是不行了。

 

然后,在touchMoved函数中,实现了camera的旋转。

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

bool OgreFramework::touchMoved(const OIS::MultiTouchEvent &evt)

{

    OIS::MultiTouchState state = evt.state;

    int origTransX = 0, origTransY = 0;

    switch(m_pCamera->getViewport()->getOrientationMode())

    {

        case Ogre::OR_LANDSCAPELEFT:

            origTransX = state.X.rel;

            origTransY = state.Y.rel;

            state.X.rel = -origTransY;

            state.Y.rel = origTransX;

            break;

            

        case Ogre::OR_LANDSCAPERIGHT:

            origTransX = state.X.rel;

            origTransY = state.Y.rel;

            state.X.rel = origTransY;

            state.Y.rel = origTransX;

            break;

            

            // Portrait doesn't need any change

        case Ogre::OR_PORTRAIT:

        default:

            break;

    }

    m_pCamera->yaw(Degree(state.X.rel * -0.1));

    m_pCamera->pitch(Degree(state.Y.rel * -0.1));

    

    return true;

}

大部分代码是为了不同的设备方向而进行的参数调整,其实主要也就是Camera的yaw,pitch旋转而已,有意思的是,touch参数里面的相对值的存在,使得旋转速度的设定非常简洁。

 

调试信息输出

框架类中剩下的还有点用的东西就是调试信息的输出了,代码如下:

//|||||||||||||||||||||||||||||||||||||||||||||||

void OgreFramework::updateStats()

{

    static String currFps = "Current FPS: ";

    static String avgFps = "Average FPS: ";

    static String bestFps = "Best FPS: ";

    static String worstFps = "Worst FPS: ";

    static String tris = "Triangle Count: ";

    static String batches = "Batch Count: ";

    

    OverlayElement* guiAvg = OverlayManager::getSingleton().getOverlayElement("Core/AverageFps");

    OverlayElement* guiCurr = OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");

    OverlayElement* guiBest = OverlayManager::getSingleton().getOverlayElement("Core/BestFps");

    OverlayElement* guiWorst = OverlayManager::getSingleton().getOverlayElement("Core/WorstFps");

    

    const RenderTarget::FrameStats& stats = m_pRenderWnd->getStatistics();

    guiAvg->setCaption(avgFps + StringConverter::toString(stats.avgFPS));

    guiCurr->setCaption(currFps + StringConverter::toString(stats.lastFPS));

    guiBest->setCaption(bestFps + StringConverter::toString(stats.bestFPS)

                        +" "+StringConverter::toString(stats.bestFrameTime)+" ms");

    guiWorst->setCaption(worstFps + StringConverter::toString(stats.worstFPS)

                         +" "+StringConverter::toString(stats.worstFrameTime)+" ms");

    

    OverlayElement* guiTris = OverlayManager::getSingleton().getOverlayElement("Core/NumTris");

    guiTris->setCaption(tris + StringConverter::toString(stats.triangleCount));

    

    OverlayElement* guiBatches = OverlayManager::getSingleton().getOverlayElement("Core/NumBatches");

    guiBatches->setCaption(batches + StringConverter::toString(stats.batchCount));

    

    OverlayElement* guiDbg = OverlayManager::getSingleton().getOverlayElement("Core/DebugText");

    guiDbg->setCaption("");

}

都是获取到响应的UI元素,然后进行输出,没有太多好讲的。

 

框架驱动代码

上述的框架还不足以构成一个完成的程序,仅仅是跨平台实现中干了一些脏活,可以跨平台的部分,我们还需要实际的驱动代码来使用这个框架。

这些代码又分两个部分,DemoApp类和AppDelegate类。

DemoApp:

class DemoApp : public OIS::KeyListener

{

public:

    DemoApp();

    ~DemoApp();

    void startDemo();

    void setupDemoScene();

    void setShutdown(bool flag) { m_bShutdown = flag; }

    

    bool keyPressed(const OIS::KeyEvent &keyEventRef);

    bool keyReleased(const OIS::KeyEvent &keyEventRef);

private:

    void runDemo();

    Ogre::SceneNode*            m_pCubeNode;

    Ogre::Entity*                m_pCubeEntity;

    bool                        m_bShutdown;

};

 

很简洁也很直接的定义,没有去继承使用Ogre的ExampleApplication等类。仅仅继承了KeyListener

,响应keyPressed

keyReleased

,而在iPhone中我们又不用管。

那么,需要看到就是真实创建天空盒和那个癞蛤蟆头的部分了。

 

void DemoApp::startDemo()

{

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", this, 0))

        return;

    

    m_bShutdown = false;

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    setupDemoScene();

    runDemo();

}

 

此函数完成了前面讲到的OgreFramework的创建及初始化,于是,此时,我们Ogre程序需要的root,scene manager, viewport,camera,等东西都已经有了。

下面直接调用setupDemoScene来创建了场景,然后通过runDemo来运行程序(即进入主循环)。

 

void DemoApp::setupDemoScene()

{

    OgreFramework::getSingletonPtr()->m_pSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");

    OgreFramework::getSingletonPtr()->m_pSceneMgr->createLight("Light")->setPosition(75,75,75);

    m_pCubeEntity = OgreFramework::getSingletonPtr()->m_pSceneMgr->createEntity("Cube", "ogrehead.mesh");

    m_pCubeNode = OgreFramework::getSingletonPtr()->m_pSceneMgr->getRootSceneNode()->createChildSceneNode("CubeNode");

    m_pCubeNode->attachObject(m_pCubeEntity);

}

 

因为初始化完了,那么创建场景的部分就简单了,如上所示,也就几句代码。属于Ogre常规操作,也就不多说了。

 

//|||||||||||||||||||||||||||||||||||||||||||||||

void DemoApp::runDemo()

{

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Start main loop...");

    

    double timeSinceLastFrame = 0;

    double startTime = 0;

    OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();

    

    while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown())

    {

        if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isClosed())m_bShutdown = true;

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

        Ogre::WindowEventUtilities::messagePump();

#endif    

        if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())

        {

            startTime = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU();

                    

#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE

            OgreFramework::getSingletonPtr()->m_pKeyboard->capture();

#endif

            OgreFramework::getSingletonPtr()->m_pMouse->capture();

            OgreFramework::getSingletonPtr()->updateOgre(timeSinceLastFrame);

            OgreFramework::getSingletonPtr()->m_pRoot->renderOneFrame();

        

            timeSinceLastFrame = OgreFramework::getSingletonPtr()->m_pTimer->getMillisecondsCPU() - startTime;

        }

        else

        {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

           Sleep(1000);

#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE

           sleep(1000);

#endif

        }

    }

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Main loop quit");

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Shutdown OGRE...");

}

看完这里,代码上是没有什么疑问了,主循环嘛。。。。。。。。不过,对于代码实现的问题上,还是有些问题的,从前面的代码来看,主循环通过

    while(!m_bShutdown && !OgreFramework::getSingletonPtr()->isOgreToBeShutDown())

控制,而且里面完全没有帧率控制代码。。。。也没有使用Ogre在教程中提倡的使用FrameListeners的frameRenderingQueued

回调函数来完成update,这个感觉比较奇怪。

而假如是通过

if(OgreFramework::getSingletonPtr()->m_pRenderWnd->isActive())

即,是否Actice来控制帧率也不正常,这个应该是窗口在后台不需要处理输入的时候使用的,而且一次sleep了1秒钟,也不可能胜任这个工作。

带着疑问调试此函数的代码,发现根本没有调用此函数。。。。。。-_-!

这里搞这么多#if OGRE_PLATFORM != OGRE_PLATFORM_IPHONE,结果iPhone中根本不调用,还是比较具有迷惑性的。。。。。。

 

真实的版本,当然就看AppDelegate了,AppDelegate是ObjC类。。。。。。看到这里,突然感觉,我虽然仅仅是最近用了几个月的ObjC,但是因为最近一直在用Objc,竟然感觉比用了好几年的C++更加亲切了。。。。。-_-!

 

AppDelegate

在main函数中,首先看到AppDelegate的使用:

int main(int argc, char **argv)

#endif

{

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  int retVal = UIApplicationMain(argc, argv, @"UIApplication", @"AppDelegate");

  [pool release];

  return retVal;

#else

}


以,我原本以为AppDelegate就是一个从ObjC到DemoApp的一个小外壳而已,处理一些简单的delegate回调,结果发现根本不是这
样,在iPhone版本的示例程序中,AppDelegate才是实际的DemoApp,而原来DemoApp,其实AppDelegate只是使用了其
setup场景的部分而已。原来我也是只猜到了开始,但是猜不到这个结果。。。。。。。

在applicationDidFinishLaunching中经过了一些在iPhone中必须进行的一些window,view操作后,直接进入了主题,go函数,看看go函数:

- (void)go {

  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  try {

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", &demo, 0))

      return;

    

    demo.setShutdown(false);

    

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    

    demo.setupDemoScene();

    OgreFramework::getSingletonPtr()->m_pRenderWnd->resetStatistics();

    

    if (mDisplayLinkSupported)

    {

     
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against
earlier versions will result in a warning, but can be dismissed

     
// if the system version runtime check for CADisplayLink exists in
-initWithCoder:. The runtime check ensures this code will

      // not be called in system versions earlier than 3.1.

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      

      mDisplayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderOneFrame:)];

      [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    }

    else

    {

      mTimer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)(1.0f / 60.0f) * mLastFrameTime

                                                target:self

                                              selector:@selector(renderOneFrame:)

                                              userInfo:nil

                                               repeats:YES];

    }

  } catch( Ogre::Exception& e ) {

    std::cerr << "An exception has occurred: " <<

    e.getFullDescription().c_str() << std::endl;

  }

 

  [pool release];

}

 

原来这才是真正驱动OgreFramework的地方啊。。。。。。前面那都是迷惑人的。。。。

 

    new OgreFramework();

    if(!OgreFramework::getSingletonPtr()->initOgre("DemoApp v1.0", &demo, 0))

      return;

    

    demo.setShutdown(false);

    

    OgreFramework::getSingletonPtr()->m_pLog->logMessage("Demo initialized!");

    

    demo.setupDemoScene();

 

在iPhone版本中,在go函数中进行了OgreFramework的创建及初始化,然后调用demo的setupDemoScene进行场景的创建(前面分析的也就这一部分是对的了。。。。。。其他demo部分分析仅适用于其它版本)。

 

    if (mDisplayLinkSupported)

    {

     
// CADisplayLink is API new to iPhone SDK 3.1. Compiling against
earlier versions will result in a warning, but can be dismissed

     
// if the system version runtime check for CADisplayLink exists in
-initWithCoder:. The runtime check ensures this code will

      // not be called in system versions earlier than 3.1.

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      

      mDisplayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderOneFrame:)];

      [mDisplayLink setFrameInterval:mLastFrameTime];

      [mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    }

 

可以看到此处是通过iPhone的CADisplayLink类来完成刷新及帧率控制的,(前面还特意检查了版本,以判断当前iPhone版本是否支持此特性,3.1以后才有的东西)而renderOneFrame里面的内容很简单,这里就不讲了。

 

看看CADisplayLink吧,在apple的文档中,有如下描述:

A
CADisplayLink object is a timer object that allows your application to
synchronize its drawing to the refresh rate of the display.

很明显,这个iPhone平台特定的东西估计要比Ogre跨平台的FrameListener要好用一些,所以此基础框架程序中使用了这个iPhone原生的东西。

不过这个代码有个问题:

      mDate = [[NSDate alloc] init];

      mLastFrameTime = -[mDate timeIntervalSinceNow];

      [mDisplayLink setFrameInterval:mLastFrameTime];

 

此时mLastFrameTime一般是个远小于1的变量,而在apple的文档中有如下描述:

frameInterval

The number of frames that must pass before the display link notifies the target again.

@property(nonatomic) NSInteger frameInterval

Discussion

The
default value is 1, which results in your application being notified at
the refresh rate of the display. If the value is set to a value larger
than 1, the display link notifies your application at a fraction of the
native refresh rate. For example, setting the interval to 2 causes the
display link to fire every other frame, providing half the frame rate.

Setting this value to less than 1 results in undefined behavior and is a programmer error.

 


意最后一句。。。。。。设置成小于1的值会导致未定义行为,并且是一个错误。。。。。既然默认是1,那么其实后面的frameInterval根本不用设
置。(原程序能够正常的运行,估计apple还是进行了一定的错误处理,当小于1时还是设置成1了)为了安全起见,这一句还是删掉吧。事实上,删掉后,运
行还是正常,此时使用默认值1,也就是每次显示刷新调用一次此函数。

 

相对的,看Cocos2D for iPhone的代码,现在默认的director类CCDisplayLinkDirector,对DisplayLink的使用实现代码如下:

    int frameInterval = (int) floor(animationInterval_ * 60.0f);

    

    CCLOG(@"cocos2d: Frame interval: %d", frameInterval);

    displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(preMainLoop:)];

    [displayLink setFrameInterval:frameInterval];

    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

 


意,这里的animationInterval是秒为单位的表示时间间隔的变量,但是这里没有直接使用此变量来设定frameInterval,而是乘以
了60,(Cocos认为iPhone设备的刷新率近似为60)应该来说,这才是正确用法。。。。。。。我看来可以向Ogre的开发组提交个bug
了。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

从源码编译CEGUI for OGRE 的配置

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

   

    现在在国内做游戏,似乎怎么都绕不开OGRE和CEGUI的学习,因为他们实在是太流行了。。。。。OGRE在Google中搜索game engine长期排在第一,而CEGUI又几乎是OGRE的官方UI。。。毕竟不是盖的。我第一份工作的时候就做过一些CEGUI相关的工作,(但是那时候引擎不是OGRE)但是一直没有太深入的学习,然后在游戏开发的路上绕了很大一圈,接触了OpenGL(ES),以及各色2D,3D引擎,最后似乎还是回到了OGRE和CEGUI,所以还是有些感慨。。。。。。

    当自己需要从头开始做某些基础工作的时候,与拿着成熟的框架和工程感觉还是有些不一样的。比如CEGUI和OGRE的配置。。。。。。。

    主要参考资料来自于《Building CEGUI for Ogre / OgreRenderer
》。

    现在(2010.9.13)CEGUI的最新版是0.72,到这里下载
。(源码版本)

    然后,将CEGUI解压到某个地方。我这里选择的是OGRE的目录下。

此时,CEGUI-0.7.2/projects/premake中可以看到批处理build_vs2008.bat,运行一下就可以得到想要的VS2008工程,第一次我尝试的时候编译此工程,然后拷贝相关的lib,dll到OGRE的相关目录,会发现还是少一个文件,debug版本是CEGUIOgreRenderer_d.lib,然后我发现还需要配置。这里感觉就没有那么直观了。。。。。。。。。不属于work out of box,这也是本文写作的唯一有效目的。。。。。

配置:

1。在批处理的目录下的config.lua文件中,修改如下两行内容:

OGRE_PATHS = { "..", "include/OGRE", "lib" }

OIS_PATHS = { "..", "include/OIS", "lib" }

这里第一个字符串的路径根据你将CEGUI解压的地方来配置,我这里由于CEGUI已经解压到OGRE的目录下,所以仅仅使用父目录就可以了。

2。在config.lua中,将OGRE_RENDERER那一行设为true,如下:

OGRE_RENDERER = true

3。在新版的OGRE中增加了对boost的依赖,所以还需要配置一下boost。(修改CEGUI_EXTRA_PATHS的内容)

我这里是:

CEGUI_EXTRA_PATHS = {

     { "../boost_1_42", "", "lib", "CEGUIOgreRenderer" },

}

此时会发现CEGUI的solution中会多出一个CEGUIOgreRenderer的工程。并且,经过前面的配置,路径都已经配置好了。

这里有些惊喜的是,发现了irrlicht相关的东西,呵呵,将来也可以尝试,个人对irrlicht的简单易用也印象深刻,不知道在学了OGRE后,下次还有没有机会再次使用。

然后,编译就好了,会报一堆的警告。。。。。。。。。。。。。感觉这些代码写的都有问题。。。。。在公司的代码里面,经常是一句warning都不能有的,而且这些waring都是类型转换警告。。。。估计在某些时候肯定会有问题。。。暂时不管了。

事实上,最后还会有个错误,

LINK : fatal error LNK1104: cannot open file 'OgreMain_d.lib'

因为配置的时候好像不能分debug/release来配置,(前面的文档没有讲这一点,我也没有深究了。。。。也许有办法吧)而OGRE的lib目录事实上又分debug/release子目录的。。。。。所以,其实看起来蛮自动化的配置,最后还是少不了手动干预一下。

手动修改lib目录后,问题解决。

此时将编译出来的lib,dll都拷贝到OGRE的相关目录,(因为我不准备修改CEGUI,所以简单的就拷贝了,需要修改CEGUI的,可以直接修改CEGUI工程配置,设置为编译后拷贝到相应目录)就可以直接在OGRE中使用CEGUI了,只需要再配置工程的CEGUI include目录就好。

然后,当遇到过这么多坑以后,满以为总该顺利了。。。。。。事实上,还有一个大坑在前面等着你,在最新的CEGUI版本中,你会遇到“应用程序正常初始化(0xc0150002)失败”错误,而且不会给你任何头绪。。。。。其实我费了这么多劲,非要从源码编译CEGUI和OGRE,而不是使用各自的SDK,就是因为使用SDK的时候碰到这个问题了,从经验判断应该是库的编译版本不匹配的问题,结果我自己将所有的源码都编译了一次了,还是有问题。。。。。。。。。无奈之余,在网上搜索了一下,碰到这个问题的人还不是少数。

这个哥们描述的背后的故事
。。。。。还提供了hack解决方案,牛,可惜我是用VS2008的,VS2005那个补丁不适合我,运行安装不了,我也还是希望通过正常的补丁途径解决。而这个哥们提供了完善的解决方案
。基本上,简单的说,VS2008的解决方案就是下载正确的依赖文件VC90
那个,或者直接下CEGUI SDK VS2008的SDK
,只是千万不要下VC80任何相关的东西。。。。(我很郁闷MS竟然不让VS2008在这种程度上支持VS2005,起码也能够让任何VS2005编译的东西在VS2008得到直接的支持啊)然后为自己的VS2008打上SP1补丁,就好了。这个问题真的折腾了我挺久,希望大家不要再继续被折腾了。。。。。。。。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

微软内部都对HTML5和Silverlight的未来举棋不定?

    今天看到Irrlicht作者Ambiera推的一则有意思的消息,虽然其实与iPhone开发关系不大,但是因为牵涉到Windows Phone 7和HTML5,我还是大概看完了。

    消息原地址来自于:http://bit.ly/d3wEqD

     大概的含义是某人刚到MS总部一周,发现了MS内部在HTML5和SilverLight技术上的派系之争。并且,提到WPF已经死掉。

"Right now there’s a faction war inside Microsoft over HTML5 vs
Silverlight. oh and WPF is dead.. i mean..it kind of was..but now..
funeral."

 

文中还提到,HTML5是WPF的替代者,并且MS还是会对HTML执行它惯用的"拥抱并且扩展"(Embrace and extend")战略,然后分支HTML5,用本地化的Windows API来特殊化Windows下的JS/HTML5。

“HTML5 is the replacement for WPF.. IE team want to fork the HTML5 spec by bolting on custom windows APi’s via JS/HTML5”


从这个观点来看,SilverLight会边缘化,但是同时也看到了新的信息,因为新的Windows Phone 7中,普通app的开发平台都是SilverLight了,可见MS并不希望SilverLight边缘化。


这个其实并不是我最感兴趣的,最有意思的是,Hadi Hariri在他的博客上发表了一篇反SilverLight的文章。“WTF you got against Silverlight?!”

见:http://tinyurl.com/3x7r8p8

文中提到了很多有趣的观点,文后的评论质量更加是非常高,很多评论的信息量已经足够独立成文了,想到国内的"顶顶族"和“家具党”,我真是感到了国际差距。。。。。

 

文章和评论可以看出,国外对MS那种老是完全放弃老技术对开发者的伤害还是很大的,虽然从技术革新来上看,这是技术上很大胆和进步。另外,国外也的确有人很注意跨平台(CP),谈及MS的时候,Linux和MacOS都有提及,甚至有人说在做网页开发的时候,公司里面几乎没有后人真的装Windows来开发,仅仅通过虚拟机装Windows XP用于测试IE。感觉国外也有人不希望与MS"绑定"在一起.

最激进的MS反对者提出,Windows Phone 7最终没有会用,就是因为开发者都不想光是为了Windows Phone 7的开发而迁移到SilverLight平台,所以会鲜有开发者支持,MS有这个功夫,不如多花点时间
把自己的其他东西弄好

当然,也有人支持MS的,
具体内容大家自己去看吧,其实最感叹的是国外这些哥们谈论完全不同技术问题,并且带有派系之针的时候纯粹是就技术论技术,并且一条一条论据摆出来分析,结合周围实际情况来讲,我最疑问的是,为啥这样讨论都没有带骂娘的人生攻击呢?。。。。。。。。

 

 

背景参考:在IE9中,MS会支持新的HTML5技术,这是与其SilverLight直接竞争的技术,同时,MS新推的Windows Phone 7中,普通app的开发平台是Silverlight。。。。。。

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记 完结篇(7) 渲染流程

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

   本文应该是该系列的最后一篇,看看一个物体在Orx中从载入到显示的完整流程。

   在Orx,渲染部分,一直与object纠缠在一起,一般不直接由外部使用。最主要的函数是2个:

orxDisplay_LoadBitmap

orxDisplay_BlitBitmap

假如还需要加一个,那就是

orxDisplay_ClearBitmap

事实是向作者讨教过显示一个位图最快的方式,大概是下面这个样子的:

void
 GameApp::Update(const
 orxCLOCK_INFO *_pstClockInfo, void
 *_pstContext) {

    //m_states.top()->Update(_pstClockInfo->fDT);

  orxDisplay_ClearBitmap(orxDisplay_GetScreenBitmap(), orx2RGBA(0
, 0
, 0
, 0
));

  orxDisplay_BlitBitmap(orxDisplay_GetScreenBitmap(), spstBitmap, 100.0f
, 000.0f
, orxDISPLAY_BLEND_MODE_ALPHA);

}

orxSTATUS GameApp::Init() {

  spstBitmap = orxDisplay_LoadBitmap("Dragon.png"
);

  //orxDisplay_SetDestinationBitmap(orxDisplay_GetScreenBitmap());

  orxCLOCK *pstClock = orxClock_Create(orx2F(0.05f
), orxCLOCK_TYPE_USER);

  

  /*
 Registers our update callback
*/

  orxClock_Register(pstClock, sUpdate, orxNULL, orxMODULE_ID_MAIN, orxCLOCK_PRIORITY_NORMAL);

  return
 orxSTATUS_SUCCESS;

}

也就是说,事实上也可以在Orx不通过配置,直接通过API,调用上面的3个函数完成图形的绘制。

 

orxDisplay_LoadBitmap

图形信息载入后保存的结构是:
/*
* Internal bitmap structure

 */

struct
 __orxBITMAP_t

{

  GLuint                    uiTexture;

  orxBOOL                   bSmoothing;

  orxFLOAT                  fWidth, fHeight;

  orxU32                    u32RealWidth, u32RealHeight, u32Depth;

  orxFLOAT                  fRecRealWidth, fRecRealHeight;

  orxCOLOR                  stColor;

  orxAABOX                  stClip;

};

在图形的载入过程中,在Orx中使用了SOIL这个外部的库,用于支持多种图形格式,而又可以不直接与jpeg和png的那一堆解压库打交道,说实话,就我使用那些库的感觉来说,为了通用及功能强大,API还是太复杂了一些,而且文档并不详细,而是用SOIL,Orx仅仅用

  /* Loads image */

  pu8ImageData = SOIL_load_image(_zFilename, (int *)&uiWidth, (int *)&uiHeight, (int *)&uiBytesPerPixel, SOIL_LOAD_RGBA);

这么一句,就支持了多种图形文件。

在orxDisplay_LoadBitmap的函数调用中,对图形的宽和高还是进行了修正,代码如下:

      /* Gets its real size */

      uiRealWidth   = orxMath_GetNextPowerOfTwo(uiWidth);

      uiRealHeight  = orxMath_GetNextPowerOfTwo(uiHeight);

使得宽高都位置在2的幂上,以支持更多的显卡。

 

然后保存好图形的相关信息,手工调用OpenGL进行纹理绑定:

      /*
 Creates new texture
*/

      glGenTextures(1
, &pstResult->uiTexture);

      glASSERT();

      glBindTexture(GL_TEXTURE_2D, pstResult->uiTexture);

      glASSERT();

      glTexImage2D(GL_TEXTURE_2D, 0
, GL_RGBA, pstResult->u32RealWidth, pstResult->u32RealHeight, 0
, GL_RGBA, GL_UNSIGNED_BYTE, pu8ImageBuffer);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (pstResult->bSmoothing != orxFALSE) ? GL_LINEAR : GL_NEAREST);

      glASSERT();

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (pstResult->bSmoothing != orxFALSE) ? GL_LINEAR : GL_NEAREST);

      glASSERT();

用下面的语句删除临时载入的资源:

    /* Deletes surface */

    SOIL_free_image_data(pu8ImageData);

 

最后图形的相关信息,包括通过OpenGL载入的纹理的句柄,都保存__orxBITMAP_t
结构中。

 

orxDisplay_ClearBitmap

主要就是如下几句:

    /*
 Clears the color buffer with given color
*/

    glClearColor((1.0f
 / 255.f
) * orxU2F(orxRGBA_R(_stColor)), (1.0f
 / 255.f
) * orxU2F(orxRGBA_G(_stColor)), (1.0f
 / 255.f
) * orxU2F(orxRGBA_B(_stColor)), (1.0f
 / 255.f
) * orxU2F(orxRGBA_A(_stColor)));

    glASSERT();

    glClear(GL_COLOR_BUFFER_BIT);

 

只要了解OpenGL,这个没有什么好说的。

 

 

orxDisplay_BlitBitmap

    此函数是渲染流程中最主要的函数,其中又调用了如下函数完成绘制:

orxDisplay_GLFW_DrawBitmap(_pstSrc, _eSmoothing, _eBlendMode);

 

 

orxDisplay_GLFW_DrawBitmap

orxDisplay_GLFW_DrawBitmap分两部分:

1。用orxDisplay_GLFW_PrepareBitmap指定当前的纹理,及通过当前渲染的配置,调整OpenGL渲染状态,比如是否抗锯齿,是否应用定点色等。

2。实际进行纹理的映射,然后最终调用

/* Draws arrays */

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

完成图形的绘制。

全系列小结

    到这里,全部的Orx源代码阅读就结束了。Orx算是一个相对较小,实现游戏相关概念不多,但是相对完整,并且有特色的的一个游戏引擎了。完整的看下来,会发现,在2D游戏引擎中,其实渲染的这一部分代码非常少,基本上也就是一个OpenGL使用纹理映射显示位图的翻版,(可参考这里
)主要的部分在于整个游戏引擎的组织。这一点上,作为完全使用C语言完成的Orx来说,代码的组织上还是非常有可借鉴之处的。每个单独的模块都是用几个关键的结构,构成一个局部的(单文件中)全局结构实现,然后仅对外开放必要的接口,基本上能做到仅仅看到结构,就能了解整个模块的大概实现,看起来非常的清晰。在部分模块的实现中,也借鉴的面向对象的实现方式,实现了可配置的简单的update及init,setup等操作。这些都是引擎中的亮点。

    假如说Orx有什么缺点的话,通过通读代码,我最大的感觉就是因为其实现的大量可配置的特点,给引擎带来的太多额外的负担,比如update,init,setup等模块的实现,几乎都是从这个目的出发的,是引擎变得复杂了很多,而且,因为这个关系,引擎内部之间的模块设计,从物理设计上来看,太多的交错,离正交太远。

    这些可能是从一开始作者对Orx的数据驱动的理念是相关的,在作者看来,外部使用者甚至就不应该直接使用其更多的API,而是直接仅仅使用配置。而从我个人的感觉,已经在推广Orx的过程中一些中国使用者的反馈来看,在没有合适编辑的情况下,config的配置负担在Orx比编写和修改代码的负担要重更多,而且总体结合起来的易用性及开发的速度上面并不比一个不需要配置驱动但是API设计合理的传统2D引擎要强。而且,在使用上并没有获得更好的易用性,在实现上由于一开始的理念存在,也使得实现更加复杂。个人感觉有点得不偿失。Orx的设计是超前,但是目前来看,传统之所以成为传统,还有有其道理所在。

    个人认为,Orx可改进的方向有几个:

其一,Orx的底层就是应该通过传统的手法去构建,不要弄的配置那么深入,各个模块需要那么灵活的配置,实现上的负担可以减轻很多。并且,这一部分的API设计也是可以对外的。也有文档指明各个API的搭配使用,外部使用者可以在完全不使用配置的情况下,将Orx作为一个普通的不是数据驱动的引擎来使用。虽然这个比较不符合作者的初衷。

其二,配置驱动的设计完全构建在上面那一层传统的不是数据驱动的那一层游戏引擎上,只要下层的API设计合理,这完全可以做到。对于普通使用者来说,学习曲线也可以更加平缓,在学习和了解了底层的API使用后,配置仅仅是用于加速自己设计的实现,而不是仅仅知道配置,一旦某个地方的配置出现隐晦的错误就一筹莫展(这个问题我自己和很多中国用户都出现过),因为了解底层的API 的使用,出现类似问题,通过在上层的调试,(即读取配置使用底层接口这一层)就能很容易的发现。而且,配置的使用不应该成为强制性的,这仅仅是使用者提高开发速度的一种方式,假如使用者认为底层的API已经足够使用,完全可以不用配置层。前面这两条说简单点就是将数据驱动与传统的接口相分离,而不是真的作为引擎的强制项。

其三,配置其实不能完全等同与一般意义的数据,需要手工编写的格式严格的配置,会给使用者带来很大的负担,我有时甚至觉得写代码比写配置更加容易,起码出错了更加方便调试。这些问题就需要能够保证生成正确配置的工具来解决了。作为了游戏引擎,Orx在这方面还比较欠缺,因为作者“憎恨”写UI的特点,估计这也不能靠作者来完成了,需要Orx的社区力量来完成。没有工具生成配置的配置驱动,根本就称不上真正的数据驱动,不过是将写代码的负担移到写配置而已。。。。。。。。。。。。。。。。。。

以上纯属个人看法,而历史证明,由于懂得东西不是很多,所以我的个人看法很多是不正确的,比如前面我提到的Orx的list实现很奇怪的问题
。。。。。。。。。。。。。。。。。。所以请读者不要轻信上述问题,那仅仅是一家之言。

 

原创文章作者保留版权 转载请注明原作者 并给出链接





write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(6) C语言实现的面向对象

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    这一节,看的是我感觉Orx实现的最为"有技术"的部分了,C语言实现面向的技术,在SDL的源码
中看到过一种方法,Orx实现的是另外一种,相对来说可以支撑更加复杂和灵活的结构。

在SDL中,用一个结构中的函数指针的替换来实现了面向对象的效果。在Orx中则是一种类似大量需要最基础基类的面向对象语言一样,提供了所有对象的基类(结构)orxSTRUCTURE,其他结构需要"继承"(其实在Orx是包含orxSTRUCTURE)自这个结构,然后在使用时,可以在获取orxSTRUCTURE指针的情况下获得真正的"子类"对象指针,并且使用。虽然说是一种模拟面向对象的技术,其实也有些像handle的技术。

    在Orx中大量的结构都是在这个框架之下的。

下面是Structure的ID列表,下面这些都是属于Orx“面向对象体系”的一部分。

/*
* Structure IDs

 */

typedef
 enum
 __orxSTRUCTURE_ID_t

{

  /*
 *** Following structures can be linked to objects ***
*/

  orxSTRUCTURE_ID_ANIMPOINTER = 0
,

  orxSTRUCTURE_ID_BODY,

  orxSTRUCTURE_ID_CLOCK,

  orxSTRUCTURE_ID_FRAME,

  orxSTRUCTURE_ID_FXPOINTER,

  orxSTRUCTURE_ID_GRAPHIC,

  orxSTRUCTURE_ID_SHADERPOINTER,

  orxSTRUCTURE_ID_SOUNDPOINTER,

  orxSTRUCTURE_ID_SPAWNER,

  orxSTRUCTURE_ID_LINKABLE_NUMBER,

  /*
 *** Below this point, structures can not be linked to objects ***
*/

  orxSTRUCTURE_ID_ANIM = orxSTRUCTURE_ID_LINKABLE_NUMBER,

  orxSTRUCTURE_ID_ANIMSET,

  orxSTRUCTURE_ID_CAMERA,

  orxSTRUCTURE_ID_FONT,

  orxSTRUCTURE_ID_FX,

  orxSTRUCTURE_ID_OBJECT,

  orxSTRUCTURE_ID_SHADER,

  orxSTRUCTURE_ID_SOUND,

  orxSTRUCTURE_ID_TEXT,

  orxSTRUCTURE_ID_TEXTURE,

  orxSTRUCTURE_ID_VIEWPORT,

  orxSTRUCTURE_ID_NUMBER,

  orxSTRUCTURE_ID_NONE = orxENUM_NONE

} orxSTRUCTURE_ID;

这里的对象与前面讲到的
过的module其实是有些重叠的。但是总体上,module更加偏向于实现的概念,这里的对象更加倾向于游戏中实际对应的对象,也就是更加高层一些。

共有结构:

orxSTRUCTURE这个最基础基类的结构非常简单:

/*
* Public structure (Must be first derived structure member!)

 */

typedef
 struct
 __orxSTRUCTURE_t

{

  orxSTRUCTURE_ID eID;            /*
*< Structure ID : 4
*/

  orxU32          u32RefCounter;  /*
*< Reference counter : 8
*/

  orxU32          u32Flags;       /*
*< Flags : 12
*/

  orxHANDLE       hStorageNode;   /*
*< Internal storage node handle : 16
*/

} orxSTRUCTURE;

一个ID,一个引用计数,一个标志量,一个存储节点的HANDLE。

Handle:

typedef void *                  orxHANDLE;

在C中也就用做于任何类型的指针。。。。。。。。。

在同样的头文件中,还有下面这个type枚举:

/*
* Structure storage types

 */

typedef
 enum
 __orxSTRUCTURE_STORAGE_TYPE_t

{

  orxSTRUCTURE_STORAGE_TYPE_LINKLIST = 0
,

  orxSTRUCTURE_STORAGE_TYPE_TREE,

  orxSTRUCTURE_STORAGE_TYPE_NUMBER,

  orxSTRUCTURE_STORAGE_TYPE_NONE = orxENUM_NONE,

} orxSTRUCTURE_STORAGE_TYPE;

但是基础结构中没有表示类型的变量,而handle到底表示什么类型的变量,就是这里面的几种了,list或者tree了,这个问题见下面的实现结构部分.

这里还看不出太多的东西,下面看看内部的实现结构:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/


/*
* Internal storage structure

 */

typedef
 struct
 __orxSTRUCTURE_STORAGE_t

{

  orxSTRUCTURE_STORAGE_TYPE eType;            /*
*< Storage type : 4
*/

  orxBANK                  *pstNodeBank;      /*
*< Associated node bank : 8
*/

  orxBANK                  *pstStructureBank; /*
*< Associated structure bank : 12
*/

  union

  {

    orxLINKLIST             stLinkList;       /*
*< Linklist : 24
*/

    orxTREE                 stTree;           /*
*< Tree : 20
*/

  };                                          /*
*< Storage union : 24
*/

} orxSTRUCTURE_STORAGE;

/*
* Internal registration info

 */

typedef
 struct
 __orxSTRUCTURE_REGISTER_INFO_t

{

  orxSTRUCTURE_STORAGE_TYPE     eStorageType; /*
*< Structure storage type : 4
*/

  orxU32                        u32Size;      /*
*< Structure storage size : 8
*/

  orxMEMORY_TYPE                eMemoryType;  /*
*< Structure storage memory type : 12
*/

  orxSTRUCTURE_UPDATE_FUNCTION  pfnUpdate;    /*
*< Structure update callbacks : 16
*/

} orxSTRUCTURE_REGISTER_INFO;

/*
* Internal storage node

 */

typedef
 struct
 __orxSTRUCTURE_STORAGE_NODE_t

{

  union

  {

    orxLINKLIST_NODE stLinkListNode;          /*
*< Linklist node : 12
*/

    orxTREE_NODE stTreeNode;                  /*
*< Tree node : 16
*/

  };                                          /*
*< Storage node union : 16
*/

  orxSTRUCTURE *pstStructure;                 /*
*< Pointer to structure : 20
*/

  orxSTRUCTURE_STORAGE_TYPE eType;            /*
*< Storage type : 24
*/

} orxSTRUCTURE_STORAGE_NODE;

/*
* Static structure

 */

typedef
 struct
 __orxSTRUCTURE_STATIC_t

{

  orxSTRUCTURE_STORAGE        astStorage[orxSTRUCTURE_ID_NUMBER]; /*
*< Structure banks
*/

  orxSTRUCTURE_REGISTER_INFO  astInfo[orxSTRUCTURE_ID_NUMBER];    /*
*< Structure info
*/

  orxU32                      u32Flags;                           /*
*< Control flags
*/

} orxSTRUCTURE_STATIC;

/*
**************************************************************************

 * Static variables                                                        *

 **************************************************************************
*/

/*
* static data

 */

static
 orxSTRUCTURE_STATIC sstStructure;

这里的结构就没有以前那么清晰了,以前是看到结构大概就知道实现的。
orxSTRUCTURE_STATIC
的orxSTRUCTURE_STORAGE        astStorage[orxSTRUCTURE_ID_NUMBER];

用于为每个structure结构提供bank,bank还分成两种,

一种是pstNodeBank表示的节点的bank。一种是pstStructureBank是结构本身的bank。

此结构的

  union

  {

    orxLINKLIST             stLinkList;       /**< Linklist : 24 */

    orxTREE                 stTree;           /**< Tree : 20 */

  };                                          /**< Storage union : 24 */

部分很明显与

orxSTRUCTURE_STORAGE_NODE结构对应,表示的是一个orxSTRUCTURE_STORAGE_NODE结构结构的list或者tree。因为根据Orx中list/tree的使用方法。(比较奇怪,见以前的文章

orxSTRUCTURE_STORAGE_NODE结构的第一个成员是

  union

  {

    orxLINKLIST_NODE stLinkListNode;          /**< Linklist node : 12 */

    orxTREE_NODE stTreeNode;                  /**< Tree node : 16 */

  };

正好符合要求。

然后这个节点结构的内容还包括:

  orxSTRUCTURE *pstStructure;                 /**< Pointer to structure : 20 */

  orxSTRUCTURE_STORAGE_TYPE eType;            /**< Storage type : 24 */

难道是每个结构bank分配的内存指针最后都保存在这里面吗?

假如真是这样,那与

orxSTRUCTURE_STATIC
结构成员变量orxSTRUCTURE_REGISTER_INFO不是重复吗?

因为这个结构也包含:

  orxSTRUCTURE_STORAGE_TYPE     eStorageType; /*
*< Structure storage type : 4
*/

  orxU32                        u32Size;      /*
*< Structure storage size : 8
*/

 

这个问题暂时留着,等下面看流程的时候再来验证。

 

然后orxSTRUCTURE_REGISTER_INFO这个注册信息结构中还有个update函数的指针,很突出,类型是:


/*
* Structure update callback function type

 */

typedef
 orxSTATUS (orxFASTCALL *orxSTRUCTURE_UPDATE_FUNCTION)(orxSTRUCTURE *_pstStructure, const
 orxSTRUCTURE *_pstCaller, const
 orxCLOCK_INFO *_pstClockInfo);

 

    一个结构注册后,每次还需要进行update?

    哈哈,到这里,已经解答了我的疑惑,既然module与Structure都是用于总体的管理整个Orx的结构的,为啥还需要两个这样的组织管理方式?而且很多类还是属于module,structure两个组织结构管理的?而且,上面看到Structure的列表也说了,Structure更加倾向于游戏中具体的概念,module倾向于底层实现。到了这里,再加上Orx一贯的一切自动化的思路,Structure的核心作用就很明显了!那就是管理所有需要对象的创建及update!没错,与module管理所有module的依赖,初始化,退出一样,Structure主要就是管理创建新对象及Update,这个在Orx这个非典型的游戏引擎中非常重要。在大部分游戏引擎中,我们需要手动的写主循环,然后控制主循环,并将update从主循环中一直进行下去,同时手动创建对象,但是Orx是配置驱动的,我们通过配置创建了对象,创建了一堆的东西,然后就都不管了,全丢给Orx了,Orx就是通过Structure这样的结构来统一管理的,也就是说,Structure是Orx运转的基石。同时Structure与module的更明显不同也显现出来了,Structure管理的是对象,一个Structure的"子类"可以有很多个创建出来的对象,module是管理模块,每个模块就是唯一的一个全局对象,所以需要统一的进行初始化及退出处理。

    知道了这个以后,再回头来看看Structure列表,什么感觉?很熟悉啊,原来都是config中能够配置自动创建的对象!

    这也是为什么Orx会费很大精力将所有配置能够创建的对象集中在这个Structure体系中管理了,无论其对象最终是什么,在Orx中都需要对通过配置其进行创建,update,所以提炼出了这个最终的基类,并且对所有对象进行统一的管理。

 

同时,上面遗留的问题,也有了一些思路了,既然是用于update的,那么分配出来的对象不一定就一定马上update,所有才会有orxSTRUCTURE_REGISTER_INFO与orxSTRUCTURE_STORAGE中的重复,很明显,Orx是在某个结构REGISTER(注册后)才开始update的。

 

基本的思路已经清晰了,下面通过流程来验证一下:

因为viewport总是需要创建的,从它入手:

首先看viewport的结构:


/*
* Viewport structure

 */

struct
 __orxVIEWPORT_t

{

  orxSTRUCTURE      stStructure;              /*
*< Public structure, first structure member : 16
*/

  orxFLOAT          fX;                       /*
*< X position (top left corner) : 20
*/

  orxFLOAT          fY;                       /*
*< Y position (top left corner) : 24
*/

  orxFLOAT          fWidth;                   /*
*< Width : 28
*/

  orxFLOAT          fHeight;                  /*
*< Height : 32
*/

  orxCOLOR          stBackgroundColor;        /*
*< Background color : 48
*/

  orxCAMERA        *pstCamera;                /*
*< Associated camera : 52
*/

  orxTEXTURE       *pstTexture;               /*
*< Associated texture : 56
*/

  orxSHADERPOINTER *pstShaderPointer;         /*
*< Shader pointer : 60
*/

};

符合前面
Structure
注释中说明的要求,也就是第一个结构是orxSTRUCTURE
类型的成员变量。我发现Orx最喜欢利用这样的技巧,list的node也是,tree的node也是,可能iarwain最喜欢这样使用C语言吧,不过的确很有用,此时__orxVIEWPORT_t
结构对象的指针与其第一个变量stStructure
的指针位置是完全一样的,也就是说,此指针可以直接作为一个orxSTRUCTURE
来使用,使用的时候,通过orxSTRUCTURE
类型中保留的type信息,又能还原整个对象的信息,保存的时候无论多少类型这样的对象的确只需要都保存orxSTRUCTURE
结构指针就行了。

 

 

orxViewport_Create

函数中,调用orxStructure_Create并加上自己的STRUCTURE_ID来创建了viewport。

  /* Creates viewport */

  pstViewport = orxVIEWPORT(orxStructure_Create(orxSTRUCTURE_ID_VIEWPORT));

 

这个函数虽然只有一句,但是包含的信息很多,orxStructure_Create函数就像前面说的那样,利用Structure的Bank和node的bank分别为Structure和node分配缓存。分配后直接添加在orxSTRUCTURE_STORAGE的list/tree中。

前面的orxVIEWPORT类似强转,但是其实不是,因为不会如此将一个指针直接转成一个结构(非指针)了。

#define orxVIEWPORT(STRUCTURE)      orxSTRUCTURE_GET_POINTER(STRUCTURE, VIEWPORT)

#define orxSTRUCTURE_GET_POINTER(STRUCTURE, TYPE) ((orx##TYPE *)_orxStructure_GetPointer(STRUCTURE, orxSTRUCTURE_ID_##TYPE))

这里的关键是另一个函数:


/*
* Gets structure pointer / debug mode

 * @param[in]   _pStructure    Concerned structure

 * @param[in]   _eStructureID   ID to test the structure against

 * @return      Valid orxSTRUCTURE, orxNULL otherwise

 */

static
 orxINLINE orxSTRUCTURE *_orxStructure_GetPointer(const
 void
 *_pStructure, orxSTRUCTURE_ID _eStructureID)

{

  orxSTRUCTURE *pstResult;

  /*
 Updates result
*/

  pstResult = ((_pStructure != orxNULL) && (((orxSTRUCTURE *)_pStructure)->eID ^ orxSTRUCTURE_MAGIC_TAG_ACTIVE) == _eStructureID) ? (orxSTRUCTURE *)_pStructure : (orxSTRUCTURE *)orxNULL;

  /*
 Done!
*/

  return
 pstResult;

}

传进一个orxSTRUCTURE的指针(orxStructure_Create的返回值),然后传出一个新的orxSTRUCTURE结构指针,再进行强转,感觉很怪异。

因为事实上,就上面的分析,只需要提供一个orxSTRUCTURE结构的指针指向的的确是一个"子类"对象的内存,是可以直接强转使用的。

所以这里的确怪异。

orxSTRUCTURE_MAGIC_TAG_ACTIVE这个怪异的宏定义入手:

/*
* Structure magic number

 */

#ifdef __orxDEBUG__

  #define orxSTRUCTURE_MAGIC_TAG_ACTIVE  
0xDEFACED0

#else

  #define orxSTRUCTURE_MAGIC_TAG_ACTIVE  
0x00000000

#endif

可见是为了debug准备的。并且意图是在debug的时候检验结构的eID成员变量,debug时发现此eID实际指向的是一段未分配内存(野指针时),能够返回空。

那这个0xDEFACED0

是怎么来的呢?magic嘛。。。。我也不知道,可能是作者在debug的时候特意打上去的标记,因为与主流程无关,这里不深究了。

另外,对于viewport来说,我发现在Orx中其并没有update函数,所以在一个真正的viewport创建出来的时候就先通过

eResult = orxSTRUCTURE_REGISTER(VIEWPORT, orxSTRUCTURE_STORAGE_TYPE_LINKLIST, orxMEMORY_TYPE_MAIN, orxNULL);

注册了,最后的注册的update函数是orxNULL,应该就表示不需要update了吧。

下面看一个需要update的,比如animpointer。

orxObject_UpdateAll

函数中

会遍历所有的object:

 /* For all objects */

  for(pstObject = orxOBJECT(orxStructure_GetFirst(orxSTRUCTURE_ID_OBJECT));

      pstObject != orxNULL;

      pstObject = orxOBJECT(orxStructure_GetNext(pstObject)))

并且,还会:

          if
(pstObject->astStructure[i].pstStructure != orxNULL)

          {

            /*
 Updates it
*/

            if
(orxStructure_Update(pstObject->astStructure[i].pstStructure, pstObject, pstClockInfo) == orxSTATUS_FAILURE)

            {

              /*
 Logs message
*/

              orxDEBUG_PRINT(orxDEBUG_LEVEL_OBJECT, "Failed to update structure #
%ld
 for object <
%s
>."
, i, orxObject_GetName(pstObject));

            }

          }

 

注意,这里的结构的update是pstObject
调用的。也就是说,前面配置及自动化的那么多的部分,事实上并不是直接由Orx底层的clock直接调用的,而是由object来驱动的。

这里通过遍历Structure结构中的update函数,完成了每个object里面需要update的部分的update,因为Orx的配置几乎是以object为基础的,所以这样设计非常合理。

同时,这里也真实的再现了面向对象的基础需求之一,对不同的对象使用相同的接口,并且不关心具体是哪个对象。。。。。。。这里object就不关心内部那一个Structure指针的数组变量中每个变量具体存储的真实Structure类型了,只需要用同一的update回调即可。。。。。

 

到了这里,Orx的主要组织结构module和Structure都看过了,底层的基础结构和模块也看过了。整体的Orx主干已经清晰了,具体的每个Structure,module是怎么实现的,暂时就不看了,需要的时候看一看就很清楚了。

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(5) core部分(config,event)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    有的引擎的源代码是越看越吸引人的,越看越让人惊呼设计之精巧,实现之完美的,很遗憾的,Orx的源码不是属于那一类,使用的感觉也不像那一类,反而越看越觉得其设计思想上与主流思想格格不入。。。。。在上一节
中list,tree的设计中,这种感觉达到了顶峰。。。。。但是,对于一个这样跨平台的引擎,我还是会看下去,也许看完美的作品,我只能惊叹,看不完美的作品,我却可以了解到更多值得改进的地方吧。在看Orx的源代码的过程中,起码我是对C语言怎么来组织一个相对规模较大的工程有了一些了解,对于常年使用C++的我,这方面知识其实还是相对欠缺。

Core部分主要包含5个部分,Clock,Config,Event,Locale,System。其中Clock,Config和Event比较在Orx中占很重要的地位,会看的详细点,其他略看。另外,因为Clock依赖于structure部分,会等到看完structure以后再看。

System部分主要是用于获取系统事件,以及delay的,太过简单了,就这一句话了。
Locale虽然有些意思,但是算不是Orx的核心模块,对于对整个Orx的理解没有帮助,这个留在自己需要实现多语言的时候再回来参考吧。

Event

    Event在Orx也算是比较核心的内容了,贯穿整个系统,很多功能的使用,比如物理的碰撞处理等都是通过事件响应来完成的。
先看结构:
外部结构:

/*
* Public event structure

 */

typedef
 struct
 __orxEVENT_t
{
  orxEVENT_TYPE     eType;                            /*
*< Event type : 4
*/

  orxENUM           eID;                              /*
*< Event ID : 8
*/

  orxHANDLE         hSender;                          /*
*< Sender handle : 12
*/

  orxHANDLE         hRecipient;                       /*
*< Recipient handle : 16
*/

  void
             *pstPayload;                       /*
*< Event payload : 20
*/

} orxEVENT;

内部结构:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/

/*
* Static structure

 */

typedef
 struct
 __orxEVENT_STATIC_t
{
  orxU32        u32Flags;                             /*
*< Control flags
*/

  orxHASHTABLE *pstHandlerTable;                      /*
*< Handler table
*/

} orxEVENT_STATIC;

/*
**************************************************************************

 * Module global variable                                                  *

 **************************************************************************
*/

static
 orxEVENT_STATIC sstEvent;

看了结构已经了解的差不多了,内部实现以Orx自己的hashtable为核心,实现快速的查找及事件的发送,外部以orxEVENT
结构的eType
,eID区分事件,pstPayload
用于事件实际结构的存储。
着重看两个实现:
添加事件关注的
orxSTATUS orxFASTCALL orxEvent_AddHandler(orxEVENT_TYPE _eEventType, orxEVENT_HANDLER _pfnEventHandler):



  /*
 No bank yet?
*/

  if
(pstBank == orxNULL)
  {
    /*
 Creates it
*/

    pstBank = orxBank_Create(orxEVENT_KU32_HANDLER_BANK_SIZE, sizeof
(orxEVENT_HANDLER), orxBANK_KU32_FLAG_NONE, orxMEMORY_TYPE_MAIN);

    /*
 Valid?
*/

    if
(pstBank != orxNULL)
    {
      /*
 Tries to add it to the table
*/

      if
(orxHashTable_Add(sstEvent.pstHandlerTable, _eEventType, pstBank) == orxSTATUS_FAILURE)
      {
        /*
 Deletes bank
*/

        orxBank_Delete(pstBank);
        pstBank = orxNULL;
      }
    }
  }




从以上代码我惊讶的发现,hashTable保存的不是一个个Handler的值,而直接是每个eventType对应的一个bank。bank的知识参考以前的文章

然后直接从bank中分配内存并赋值:

  /*
 Valid?
*/

  if
(pstBank != orxNULL)
  {
    orxEVENT_HANDLER *ppfnHandler;

    /*
 Creates a new handler slot
*/

    ppfnHandler = (orxEVENT_HANDLER *)orxBank_Allocate(pstBank);

    /*
 Valid?
*/

    if
(ppfnHandler != orxNULL)
    {
      /*
 Updates it
*/

      *ppfnHandler = _pfnEventHandler;

      /*
 Updates result
*/

      eResult = orxSTATUS_SUCCESS;
    }
  }
我开始很奇怪这种使用方式,直接就对分配的内存赋值
然后就不管了。后来想了想,因为bank毕竟是Orx自己的内存管理模块,这里将来对eventHandler的使用也肯定是遍历,那么Orx中就直接省去了对bank分配内存指针的保存,将来直接遍历bank了。。。。。起码现在是这样猜测的。

看Send部分后一定见分晓。
由于Send函数的源代码较短,全部贴出来了:
orxSTATUS orxFASTCALL orxEvent_Send(const
 orxEVENT *_pstEvent)
{
  orxBANK  *pstBank;
  orxSTATUS eResult = orxSTATUS_SUCCESS;

  /*
 Checks
*/

  orxASSERT(orxFLAG_TEST(sstEvent.u32Flags, orxEVENT_KU32_STATIC_FLAG_READY));
  orxASSERT(_pstEvent != orxNULL);

  /*
 Gets corresponding bank
*/

  pstBank = (orxBANK *)orxHashTable_Get(sstEvent.pstHandlerTable, _pstEvent->eType);

  /*
 Valid?
*/

  if
(pstBank != orxNULL)
  {
    orxEVENT_HANDLER *ppfnHandler;

    /*
 For all handler
*/

    for
(ppfnHandler = (orxEVENT_HANDLER *)orxBank_GetNext(pstBank, orxNULL);
        ppfnHandler != orxNULL;
        ppfnHandler = (orxEVENT_HANDLER *)orxBank_GetNext(pstBank, ppfnHandler))
    {
      /*
 Calls handler
*/

      if
((*ppfnHandler)(_pstEvent) == orxSTATUS_FAILURE)
      {
        /*
 Updates result
*/

        eResult = orxSTATUS_FAILURE;

        break
;
      }
    }
  }

  /*
 Done!
*/

  return
 eResult;
}


果然如我在Add Handler中看到Orx的操作后的猜测一样,获取bank,然后遍历bank.................虽然这样的确也能够实现功能,虽然这样可以省去一个容器变量用于存储handler,但是个人还是感觉这样的用法类似hack。。。。想象一下,你使用Win32 API的时候,直接遍历内存链是什么效果。。。。。。或者,bank在设计时就考虑了这样的用法吧。。。。即使如此,还是将bank一物两用了,又作为内存分配的缓存容器,同时还是个实际数据的缓存容器。。。。。

Config

对于以Config为驱动的Orx的来说,这应该算是最最核心的模块了,假如说Orx哪个部分我是最希望抽出来以后独立使用的话,估计也就是这个部分了。Orx的Config部分,不仅仅实现了一个普通Windows INI所需要的简单的那一部分,包括section和key=value的读取,还包括section的继承,key的引用,value的随机,vector value的读取等强大功能,很多功能甚至凌驾于Json等通用配置。当时,这样强大的config不是没有代价的,换来的是较为庞大的配置代码。光是orxConfig.c一个文件,代码行数就超过了4K。

还是先看结构:

/*
**************************************************************************

 * Structure declaration                                                   *

 **************************************************************************
*/

/*
* Config value type enum

 */

typedef
 enum
 __orxCONFIG_VALUE_TYPE_t
{
  orxCONFIG_VALUE_TYPE_STRING = 0
,
  orxCONFIG_VALUE_TYPE_FLOAT,
  orxCONFIG_VALUE_TYPE_S32,
  orxCONFIG_VALUE_TYPE_U32,
  orxCONFIG_VALUE_TYPE_BOOL,
  orxCONFIG_VALUE_TYPE_VECTOR,

  orxCONFIG_VALUE_TYPE_NUMBER,

  orxCONFIG_VALUE_TYPE_NONE = orxENUM_NONE

} orxCONFIG_VALUE_TYPE;

/*
* Config value structure

 */

typedef
 struct
 __orxCONFIG_VALUE_t
{
  orxSTRING             zValue;             /*
*< Literal value : 4
*/

  orxCONFIG_VALUE_TYPE  eType;              /*
*< Value type : 8
*/

  orxU16                u16Flags;           /*
*< Status flags : 10
*/

  orxU8                 u8ListCounter;      /*
*< List counter : 11
*/

  orxU8                 u8CacheIndex;       /*
*< Cache index : 12
*/

  union

  {
    orxVECTOR           vValue;             /*
*< Vector value : 24
*/

    orxBOOL             bValue;             /*
*< Bool value : 16
*/

    orxFLOAT            fValue;             /*
*< Float value : 16
*/

    orxU32              u32Value;           /*
*< U32 value : 16
*/

    orxS32              s32Value;           /*
*< S32 value : 16
*/

  };                                        /*
*< Union value : 24
*/

  union

  {
    orxVECTOR           vAltValue;          /*
*< Alternate vector value : 36
*/

    orxBOOL             bAltValue;          /*
*< Alternate bool value : 28
*/

    orxFLOAT            fAltValue;          /*
*< Alternate float value : 28
*/

    orxU32              u32AltValue;        /*
*< Alternate U32 value : 28
*/

    orxS32              s32AltValue;        /*
*< Alternate S32 value : 28
*/

  };                                        /*
*< Union value : 36
*/

} orxCONFIG_VALUE;

/*
* Config entry structure

 */

typedef
 struct
 __orxCONFIG_ENTRY_t
{
  orxLINKLIST_NODE  stNode;                 /*
*< List node : 12
*/

  orxSTRING         zKey;                   /*
*< Entry key : 16
*/

  orxU32            u32ID;                  /*
*< Key ID (CRC) : 20
*/

  orxCONFIG_VALUE   stValue;                /*
*< Entry value : 56
*/

  orxPAD(56
)

} orxCONFIG_ENTRY;

/*
* Config section structure

 */

typedef
 struct
 __orxCONFIG_SECTION_t
{
  orxLINKLIST_NODE  stNode;                 /*
*< List node : 12
*/

  orxBANK          *pstEntryBank;           /*
*< Entry bank : 16
*/

  orxSTRING         zName;                  /*
*< Section name : 20
*/

  orxU32            u32ID;                  /*
*< Section ID (CRC) : 24
*/

  orxU32            u32ParentID;            /*
*< Parent ID (CRC) : 28
*/

  orxS32            s32ProtectionCounter;   /*
*< Protection counter : 32
*/

  orxLINKLIST       stEntryList;            /*
*< Entry list : 44
*/

  orxPAD(44
)

} orxCONFIG_SECTION;

/*
* Config stack entry structure

 */

typedef
 struct
 __orxCONFIG_STACK_ENTRY_t
{
  orxLINKLIST_NODE    stNode;               /*
*< Linklist node : 12
*/

  orxCONFIG_SECTION  *pstSection;           /*
*< Section : 16
*/

} orxCONFIG_STACK_ENTRY;

/*
* Static structure

 */

typedef
 struct
 __orxCONFIG_STATIC_t
{
  orxBANK            *pstSectionBank;       /*
*< Section bank
*/

  orxCONFIG_SECTION  *pstCurrentSection;    /*
*< Current working section
*/

  orxBANK            *pstHistoryBank;       /*
*< History bank
*/

  orxBANK            *pstStackBank;         /*
*< Stack bank
*/

  orxLINKLIST         stStackList;          /*
*< Stack list
*/

  orxU32              u32Flags;             /*
*< Control flags
*/

  orxU32              u32LoadCounter;       /*
*< Load counter
*/

  orxSTRING           zEncryptionKey;       /*
*< Encryption key
*/

  orxU32              u32EncryptionKeySize; /*
*< Encryption key size
*/

  orxCHAR            *pcEncryptionChar;     /*
*< Current encryption char
*/

  orxLINKLIST         stSectionList;        /*
*< Section list
*/

  orxHASHTABLE       *pstSectionTable;      /*
*< Section table
*/

  orxCHAR             zBaseFile[orxCONFIG_KU32_BASE_FILENAME_LENGTH]; /*
*< Base file name
*/

} orxCONFIG_STATIC;

/*
**************************************************************************

 * Static variables                                                        *

 **************************************************************************
*/

/*
* static data

 */

static
 orxCONFIG_STATIC sstConfig;

还是那句话,看结构比看流程实在是更加有用,对于Orx来说,说了它再多的不是,它每个模块的结构之清晰,(虽然每个模块之间关系有点乱)真是很多软件设计根本没法比的。

 

所有的配置由sstConfig存储。orxHASHTABLE       *pstSectionTable; 用于快速的查找section,完整的section信息保存在stSectionList
中。

每个orxCONFIG_SECTION
结构本身又包含stEntryList
表示的list,存储的是orxCONFIG_ENTRY
结构,表示一组key=value对。zKey
自然表示完整的key,u32ID
表示CRC过的key。value保存在orxCONFIG_VALUE结构的
成员变量stValue
中。

orxCONFIG_VALUE
就是很多时候能在C/C++语言中看到的万能结构,用于保存一个可能为各种类型的变量。

 

zValue用于保存原始的value字符串,然后eType表示类型,具体的value由此type决定,保存在下面两个union中。

出去一些辅助成员,(比如stack,CurrentSection
等用于暂时缓存现在config操作的成员变量),主要的config变量是多层容器结构。

由config保存着section容器列表,每个section容器又包含entry容器列表,entry容器保存key=value对。

其实看到结构已经对Orx的Config了解的很清楚了,但是回头想想,对于类似的需求,真的也不会以其他方式实现,还不就是上层的东西包含一个下层东西的列表啊。

 

另外,section结构中是包含一个u32ParentID变量的,用于表示继承自哪个父section。

没有在结构中看到Orx拥有的引用,随机等内容,说明这些都是都是在解析config的时候搞定的。

下面只看一个函数orxConfig_Load,搞定这一个,基本其他也不用看了。

 

主要解析config的过程是下列这样一个for循环结构


    for
(u32Size = orxFile_Read(acBuffer, sizeof
(orxCHAR), orxCONFIG_KU32_BUFFER_SIZE, pstFile), u32Offset = 0
, bFirstTime = orxTRUE;
        u32Size > 0
;
        u32Size = orxFile_Read(acBuffer + u32Offset, sizeof
(orxCHAR), orxCONFIG_KU32_BUFFER_SIZE - u32Offset, pstFile) + u32Offset, bFirstTime = orxFALSE)
    {

.............

 

很夸张的for运用方式。。。。。。。。但是理解上没有什么问题,也就是遍历读取文件到一个叫acBuffer的buffer中。

然后遍历buffer中的每个字符开始解析:

每一行的解析直到这个时候为止:

        /*
 Comment character or EOL out of block mode or block character in block mode?
*/

        if
((((*pc == orxCONFIG_KC_COMMENT)
          || (*pc == orxCHAR_CR)
          || (*pc == orxCHAR_LF))
         && (bBlockMode == orxFALSE))
        || ((bBlockMode != orxFALSE)
         && (*pc == orxCONFIG_KC_BLOCK)))
        {

............

上面也就是找到了行结尾了。

当发现当前行有section的时候(通过[]来判断)

用 orxConfig_SelectSection(pcLineStart + 1);来完成section的创建及选择(假如已经创建了的话)

这样的意义在于无论多少个section,最后都会拼接成一个,并且实现Orx配置中后面出现的配置会覆盖前面的配置的特性。

static orxINLINE orxCONFIG_SECTION *orxConfig_CreateSection(const orxSTRING _zSectionName, orxU32 _u32SectionID, orxU32 _u32ParentID)
中有两句关键的代码:

          orxLinkList_AddEnd(&(sstConfig.stSectionList), &(pstSection->stNode));

          /* Adds it to table */
          orxHashTable_Add(sstConfig.pstSectionTable, _u32SectionID, pstSection);

以此完成section的添加,如看结构的时候分析的一样,分别添加进list和hashTable中。

 

 

而当发现当前行有key和value的时候:

if((pcKeyEnd != orxNULL) && (pcValueStart != orxNULL))

处理一下数据,通过orxConfig_AddEntry函数,添加一个入口。

 

AddEntry中会复制key和value到config中,

其中orxConfig_ComputeWorkingValue函数中处理了value的引用,继承,随机等问题。当然,其实也没有什么技术含量,实际也就是对value进行类似上面的字符解析而已。

然后用以下语句解答了结构中的分析。

orxLinkList_AddEnd(&(sstConfig.pstCurrentSection->stEntryList), &(pstEntry->stNode));

以上代码对当前选中的section的EntryList中添加了新的entry节点,也就是表示当前的key=value对。

以上代码基本已经完成了从section列表的创建到entry列表创建。

 

 

小结

其实,开始的时候我以为中间包含的字符解析,文件包含,继承,引用等内容,还有些看头,后来看了才发现,由于都用了很特殊的字符来表示,实际也就是找到相关的字符,然后处理的过程,没有啥技术含量。。。。。。。。。大失所望。。。。。。不过回头来想想,Martin Fowler说,傻子都会写让计算机理解的代码;而优秀程序员写的是人能看懂的代码。从这个角度看,config写的是很成功的了。。。。。只是抱着商业大片的人结果看的是平淡无奇的文艺片,可能有些失望吧。

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

一些CRC算法的分析 用于 确认Orx HashTable的Bug

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

上文
我阅读了Orx自己实现的一套hashTable,经我过的初步分析,一个CRC算法作为key是有可能冲突的,但是并没有验证,作为bug提交的话,有些不够完整,所以,我写个测试程序真实的验证一下这个CRC算法,同时,也验证一下自己的分析是否正确。简而言之,就是验证此CRC算法在实际使用中,到底有无冲突的可能。

 

原理很简单,就是生成一堆字符串,然后传进此CRC算法,然后比较CRC后的值有无重复。此时我真希望可以使用Python,但是想想有写将接口使用Python API让Python使用的功夫,我都已经将测试代码写完了,于是作罢,还是老老实实的用C++吧。

 

先将整个Orx的Crc算法抽出来,如下:


#include


typedef
 unsigned
  long
          orxU32;
typedef
 char
                    orxCHAR;
#define orxCHAR_NULL              
'0'

#define orxASSERT assert

#define orxSTRING               orxCHAR *

#define orxNULL               (
0
)

static
 const
 orxU32 sau32CRCTable[256
] =
{
  0x00000000
, 0x04C11DB7
, 0x09823B6E
, 0x0D4326D9
, 0x130476DC
, 0x17C56B6B
, 0x1A864DB2
, 0x1E475005
,
  0x2608EDB8
, 0x22C9F00F
, 0x2F8AD6D6
, 0x2B4BCB61
, 0x350C9B64
, 0x31CD86D3
, 0x3C8EA00A
, 0x384FBDBD
,
  0x4C11DB70
, 0x48D0C6C7
, 0x4593E01E
, 0x4152FDA9
, 0x5F15ADAC
, 0x5BD4B01B
, 0x569796C2
, 0x52568B75
,
  0x6A1936C8
, 0x6ED82B7F
, 0x639B0DA6
, 0x675A1011
, 0x791D4014
, 0x7DDC5DA3
, 0x709F7B7A
, 0x745E66CD
,
  0x9823B6E0
, 0x9CE2AB57
, 0x91A18D8E
, 0x95609039
, 0x8B27C03C
, 0x8FE6DD8B
, 0x82A5FB52
, 0x8664E6E5
,
  0xBE2B5B58
, 0xBAEA46EF
, 0xB7A96036
, 0xB3687D81
, 0xAD2F2D84
, 0xA9EE3033
, 0xA4AD16EA
, 0xA06C0B5D
,
  0xD4326D90
, 0xD0F37027
, 0xDDB056FE
, 0xD9714B49
, 0xC7361B4C
, 0xC3F706FB
, 0xCEB42022
, 0xCA753D95
,
  0xF23A8028
, 0xF6FB9D9F
, 0xFBB8BB46
, 0xFF79A6F1
, 0xE13EF6F4
, 0xE5FFEB43
, 0xE8BCCD9A
, 0xEC7DD02D
,
  0x34867077
, 0x30476DC0
, 0x3D044B19
, 0x39C556AE
, 0x278206AB
, 0x23431B1C
, 0x2E003DC5
, 0x2AC12072
,
  0x128E9DCF
, 0x164F8078
, 0x1B0CA6A1
, 0x1FCDBB16
, 0x018AEB13
, 0x054BF6A4
, 0x0808D07D
, 0x0CC9CDCA
,
  0x7897AB07
, 0x7C56B6B0
, 0x71159069
, 0x75D48DDE
, 0x6B93DDDB
, 0x6F52C06C
, 0x6211E6B5
, 0x66D0FB02
,
  0x5E9F46BF
, 0x5A5E5B08
, 0x571D7DD1
, 0x53DC6066
, 0x4D9B3063
, 0x495A2DD4
, 0x44190B0D
, 0x40D816BA
,
  0xACA5C697
, 0xA864DB20
, 0xA527FDF9
, 0xA1E6E04E
, 0xBFA1B04B
, 0xBB60ADFC
, 0xB6238B25
, 0xB2E29692
,
  0x8AAD2B2F
, 0x8E6C3698
, 0x832F1041
, 0x87EE0DF6
, 0x99A95DF3
, 0x9D684044
, 0x902B669D
, 0x94EA7B2A
,
  0xE0B41DE7
, 0xE4750050
, 0xE9362689
, 0xEDF73B3E
, 0xF3B06B3B
, 0xF771768C
, 0xFA325055
, 0xFEF34DE2
,
  0xC6BCF05F
, 0xC27DEDE8
, 0xCF3ECB31
, 0xCBFFD686
, 0xD5B88683
, 0xD1799B34
, 0xDC3ABDED
, 0xD8FBA05A
,
  0x690CE0EE
, 0x6DCDFD59
, 0x608EDB80
, 0x644FC637
, 0x7A089632
, 0x7EC98B85
, 0x738AAD5C
, 0x774BB0EB
,
  0x4F040D56
, 0x4BC510E1
, 0x46863638
, 0x42472B8F
, 0x5C007B8A
, 0x58C1663D
, 0x558240E4
, 0x51435D53
,
  0x251D3B9E
, 0x21DC2629
, 0x2C9F00F0
, 0x285E1D47
, 0x36194D42
, 0x32D850F5
, 0x3F9B762C
, 0x3B5A6B9B
,
  0x0315D626
, 0x07D4CB91
, 0x0A97ED48
, 0x0E56F0FF
, 0x1011A0FA
, 0x14D0BD4D
, 0x19939B94
, 0x1D528623
,
  0xF12F560E
, 0xF5EE4BB9
, 0xF8AD6D60
, 0xFC6C70D7
, 0xE22B20D2
, 0xE6EA3D65
, 0xEBA91BBC
, 0xEF68060B
,
  0xD727BBB6
, 0xD3E6A601
, 0xDEA580D8
, 0xDA649D6F
, 0xC423CD6A
, 0xC0E2D0DD
, 0xCDA1F604
, 0xC960EBB3
,
  0xBD3E8D7E
, 0xB9FF90C9
, 0xB4BCB610
, 0xB07DABA7
, 0xAE3AFBA2
, 0xAAFBE615
, 0xA7B8C0CC
, 0xA379DD7B
,
  0x9B3660C6
, 0x9FF77D71
, 0x92B45BA8
, 0x9675461F
, 0x8832161A
, 0x8CF30BAD
, 0x81B02D74
, 0x857130C3
,
  0x5D8A9099
, 0x594B8D2E
, 0x5408ABF7
, 0x50C9B640
, 0x4E8EE645
, 0x4A4FFBF2
, 0x470CDD2B
, 0x43CDC09C
,
  0x7B827D21
, 0x7F436096
, 0x7200464F
, 0x76C15BF8
, 0x68860BFD
, 0x6C47164A
, 0x61043093
, 0x65C52D24
,
  0x119B4BE9
, 0x155A565E
, 0x18197087
, 0x1CD86D30
, 0x029F3D35
, 0x065E2082
, 0x0B1D065B
, 0x0FDC1BEC
,
  0x3793A651
, 0x3352BBE6
, 0x3E119D3F
, 0x3AD08088
, 0x2497D08D
, 0x2056CD3A
, 0x2D15EBE3
, 0x29D4F654
,
  0xC5A92679
, 0xC1683BCE
, 0xCC2B1D17
, 0xC8EA00A0
, 0xD6AD50A5
, 0xD26C4D12
, 0xDF2F6BCB
, 0xDBEE767C
,
  0xE3A1CBC1
, 0xE760D676
, 0xEA23F0AF
, 0xEEE2ED18
, 0xF0A5BD1D
, 0xF464A0AA
, 0xF9278673
, 0xFDE69BC4
,
  0x89B8FD09
, 0x8D79E0BE
, 0x803AC667
, 0x84FBDBD0
, 0x9ABC8BD5
, 0x9E7D9662
, 0x933EB0BB
, 0x97FFAD0C
,
  0xAFB010B1
, 0xAB710D06
, 0xA6322BDF
, 0xA2F33668
, 0xBCB4666D
, 0xB8757BDA
, 0xB5365D03
, 0xB1F740B4

};

orxU32 orxString_ContinueCRC(const
 orxSTRING _zString, orxU32 _u32CRC)
{
  register
 orxU32          u32CRC;
  register
 const
 orxCHAR  *pc;

  /*
 Checks
*/

  orxASSERT(_zString != orxNULL);

  /*
 Inits CRC
*/

  u32CRC = _u32CRC ^ 0xFFFFFFFFL
;

  /*
 For the whole string
*/

  for
(pc = _zString; *pc != orxCHAR_NULL; pc++)
  {
    /*
 Computes the CRC
*/

    u32CRC = sau32CRCTable[(u32CRC ^ *pc) & 0xFF
] ^ (u32CRC >> 8
);
  }

  /*
 Done!
*/

  return
(u32CRC ^ 0xFFFFFFFFL
);
}

 

由于在Orx中使用的时候,第2参数一直为0,所以函数实际可以改成下面这样

orxU32 orxString_ContinueCRC(const
 orxSTRING _zString)
{
  register
 orxU32          u32CRC;
  register
 const
 orxCHAR  *pc;

  /*
 Checks
*/

  orxASSERT(_zString != orxNULL);

  /*
 Inits CRC
*/

  u32CRC = 0
 ^ 0xFFFFFFFFL
;

  /*
 For the whole string
*/

  for
(pc = _zString; *pc != orxCHAR_NULL; pc++)
  {
    /*
 Computes the CRC
*/

    u32CRC = sau32CRCTable[(u32CRC ^ *pc) & 0xFF
] ^ (u32CRC >> 8
);
  }

  /*
 Done!
*/

  return
(u32CRC ^ 0xFFFFFFFFL
);
}

 

作为测试结果,我希望检验是否会在string较短时就发生冲突,并且,真的冲突的话,输出冲突的字符串。这里暂时仅测试ASCII的字符,按照Orx的实际使用,UTF8字符的冲突的可能性只会比这个高,不会比这个低。

突然觉得想出一个这样的测试算法也挺有意思的。

作为较短的函数,暂时将字符串长度定在20以下。

第一步:生成测试字符串:

这里为了简化,就全生成同样长度的小写字符串了,其实本来想遍历生成的,后来觉得太麻烦,想了想,其实没有必要,只要能找到冲突就行,不一定要遍历。


char
 *rand_str(char
 *str,const
 int
 len)
{
  char
 *c = str;
  for
(int
 i=0
; i
    *c='a'
+rand()%26
;
  }

  *c='0'
;
  return
 str;
}

用这个函数来生成随机指定长度的字符串。

 

我然后用下列函数来检验:


typedef
 hash_map<unsigned
 long
, list > resultType;
resultType result;
void
 testIt(char
* testString) {
  unsigned
 long
 crcValue = orxString_ContinueCRC(testString);

  resultType::iterator clashIt = result.find(crcValue);
  if
(clashIt != result.end()) {
    // have a clash

    list &clashList = (clashIt->second);
    clashList.push_back(testString);

    printf("Clash strings: "
);
    for
 (list::iterator it = clashList.begin();
      it != clashList.end();
      ++it) {
        printf("
%s
t
"
, it->c_str());
    }
    printf("
n
"
);
    return
;
  }

  list newVaule;
  newVaule.push_back(testString);
  result.insert(make_pair(crcValue, newVaule));
}

原理上也没有什么好说的了,就是一个hash_map<unsigned
 long
, list >

的变量,用于来保存目前测试过的所有字符串,key是CRC计算后的值,list用于存储字符串。这种用法消耗内存非常多。。。。。。。

 

驱动代码如下,需要增加测试数量的,改NumOftest是改测试字符串的长度,i的最大值是测试的次数。


int
 _tmain(int
 argc, _TCHAR* argv[])
{
  srand(time(NULL
));
  const
 int
 NumOftest = 11
;
  char
 test[NumOftest + 1
];
  for
(int
 i = 0
; i < 200000
; ++i) {
    rand_str(test, NumOftest);
    testIt(test);
  }

  system("PAUSE"
);

    return
 0
;
}

事实上, 经过测试,在上面这个级别我就测试出了4组冲突:

Clash strings: rdvohjmmqco      dzfmlbvurvy
Clash strings: ezmchjdxzvc      heugmqpnwhr
Clash strings: xuosifsuqnz      hdnklsfofvo
Clash strings: cxezqfgwkdy      hjccdfyaamc

 

20W的级别,长度为十的字符串。。。。。。。。。。。。这不是现实中可能出现的组合,出现这样的情况,Orx中的hashTable必然就会出现问题。。。。。

另外,这也说明了出现问题的概率不是小到宇宙毁灭的几率那样,谁也不能保证在Orx使用一个256长度的hashTable的时候会不会出现问题。。。。。。。。。。。。。。。。事实上,即使为了效率,也还是参考参考MPQ中暴雪对CRC的使用,或者是如一般std扩展中hashmap的使用吧。

 

 

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

非典型2D游戏引擎 Orx 源码阅读笔记(4) 用C实现的基本容器(List,HashTable,Tree)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

C语言中没有标准的容器,所以为了跨平台,Orx中自己实现了一套。

在Orx中作者分别实现了HashTable,List,Tree3个容器,虽然没有实现一个自己的String,但是为orxSTRING(实际为char*的typedef)实现了一堆的辅助函数。

下面分别来看看,为了不使本文成为一个类似algorithm in C的讲算法的文章,这里只看看使用和各容器的特性^^

List

以前有人说过,几乎每个程序员在职业生涯都会自己实现一个List,不知道是不是真这样,虽然我的确实现过。。。。。。。。。。
先看结构:

/*
* Node list structure

 */

typedef
 struct
 __orxLINKLIST_NODE_t
{
  struct
 __orxLINKLIST_NODE_t *pstNext;         /*
*< Next node pointer : 4
*/

  struct
 __orxLINKLIST_NODE_t *pstPrevious;     /*
*< Previous node pointer : 8
*/

  struct
 __orxLINKLIST_t      *pstList;         /*
*< Associated list pointer : 12
*/

} orxLINKLIST_NODE;

/*
* List structure

 */

typedef
 struct
 __orxLINKLIST_t
{
  orxLINKLIST_NODE *pstFirst;                   /*
*< First node pointer : 4
*/

  orxLINKLIST_NODE *pstLast;                    /*
*< Last node pointer : 8
*/

  orxU32            u32Counter;                 /*
*< Node counter : 12
*/

} orxLINKLIST;


从结构看,我感觉实现的有些特殊,我以前看到的一般的list实现(自然是指C语言中的)一般用一个结构就解决一切了,都是用list的node本身(一般是头结点)来表示一个list,Orx这里额外特别的抽象出了一个List结构,然后node还反过来有指针指向list,有点奇怪的是,没有看到数据存在哪里。。。。。。。。。。。然后,突然发现,我没有看到list的Create函数。。。。。。。。看了一下Orx对List的使用,原来都是直接使用orxLINKLIST
。。。。。。。。。。。。
再然后,没有地方存储数据(比如类似pData的指针)的疑惑也解开了。。。。。。所有使用这个list的地方都用一个以orxLINKLIST_NODE
为第一个成员变量的结构,然后node的指针,就相当于整个结构的指针了。。。。。。。。。。。。。
比如下面这个config的结构:
typedef struct __orxCONFIG_SECTION_t
{
  orxLINKLIST_NODE  stNode;                 /**< List node : 12 */
  orxBANK          *pstEntryBank;           /**< Entry bank : 16 */
  orxSTRING         zName;                  /**< Section name : 20 */
  orxU32            u32ID;                  /**< Section ID (CRC) : 24 */
  orxU32            u32ParentID;            /**< Parent ID (CRC) : 28 */
  orxS32            s32ProtectionCounter;   /**< Protection counter : 32 */
  orxLINKLIST       stEntryList;            /**< Entry list : 44 */

  orxPAD(44)

} orxCONFIG_SECTION;

谜底解开了,总的来说,这个List很诡异。。。。。。。。。。。。

HashTable

一个HashTable容器拥有O(1)的查找效率,被誉为20世纪最重要的编程领域发明之一。。。。。(不记得在哪看到的了)
先看看结构:
#define orxHASHTABLE_KU32_INDEX_SIZE                          256
/*
* Hash table cell definition.
*/

typedef
 struct
 __orxHASHTABLE_CELL_t
{
  orxU32                        u32Key;                       /*
*< Key element of a hash table : 4
*/

  void
                         *pData;                        /*
*< Address of data : 8
*/

  struct
 __orxHASHTABLE_CELL_t *pstNext;                      /*
*< Next cell with the same index : 12
*/

  orxPAD(12
)
} orxHASHTABLE_CELL;

/*
* Hash Table
*/

struct
 __orxHASHTABLE_t
{
  orxHASHTABLE_CELL  *apstCell[orxHASHTABLE_KU32_INDEX_SIZE]; /*
*< Hash table
*/

  orxBANK            *pstBank;                                /*
*< Bank where are stored cells
*/

  orxU32              u32Counter;                             /*
*< Hashtable item counter
*/

};


每个hashTable的数据自然是一个cell,存储在pData中,通过注释基本也能理解Orx的Hash table的结构了。
所有的数据通过apstCell[orxHASHTABLE_KU32_INDEX_SIZE];
数组来存储,并且通过某个较小的key(说明这个较小的key一定小于orxHASHTABLE_KU32_INDEX_SIZE
)来索引这个数据,假如有第一个较小的key冲突的情况,通过orxHASHTABLE_CELL
*pstNext
变量来保存成链状,并通过u32Key
这个32位的key来区别。这个结构与一般C++ STL扩展中的hashTable实现原理基本一样。(HashTable目前还不是C++标准库的内容,所以只在扩展库中存在)
谁说看结构比看流程清晰来着?说的实在是太对了。当结构是这样定的时候,流程还能玩出啥花来?

用下面这样的代码来测试一下,同时验证想法。
  orxHASHTABLE* hashTable = orxHashTable_Create(32
, orxHASHTABLE_KU32_FLAG_NONE, orxMEMORY_TYPE_MAIN);

  char
* firstKey = "FirstKey"
;
  char
* secondKey = "secondKey"
;
  char
* thirdKey = "thirdKey"
;
  orxHashTable_Add(hashTable, orxString_ToCRC(firstKey), firstKey);
  orxHashTable_Add(hashTable, orxString_ToCRC(secondKey), secondKey);
  orxHashTable_Set(hashTable, orxString_ToCRC(firstKey), secondKey);
  orxHashTable_Set(hashTable, orxString_ToCRC(thirdKey), thirdKey);
  char
* value = (char
*)orxHashTable_Get(hashTable, orxString_ToCRC(firstKey));
  orxHashTable_Delete(hashTable);

  printf("Value:
%s
"
, value);


解释:
orxHashTable_Create实际仅仅分配了内存而已。值得一提的仅仅是cell的内存使用上一节
提到的bank来分配的。
orxString_ToCRC用于将string进行CRC,没有啥好说的。只是不知道Orx的这个CRC效果怎么样,比如速度快不快,冲突几率高不高。
orxHashTable_Add中先判断是否有同样key的数据存在,存在的话会报错。
其中调用的下列函数非常关键,用于获取前面提到的较小的key,来合成数组的索引。原来就是取CRC出来的32位值的与orxHASHTABLE_KU32_INDEX_SIZE
匹配的低位而已。(虽然这里用取模会意思更加自然一些,但是一个取模操作比一个位操作慢的不是一点点)
static
 orxINLINE orxU32 orxHashTable_FindIndex(const
 orxHASHTABLE *_pstHashTable, orxU32 _u32Key)
{
  /*
 Checks
*/

  orxASSERT(_pstHashTable != orxNULL);

  /*
 Computes the hash index
*/

  return
(_u32Key & (orxHASHTABLE_KU32_INDEX_SIZE - 1
));
}

找到位置以后的操作就太自然了。

  /*
 Get the hash table index
*/

    u32Index = orxHashTable_FindIndex(_pstHashTable, _u32Key);

    /*
 Allocate a new cell if possible
*/

    pstCell = (orxHASHTABLE_CELL *)orxBank_Allocate(_pstHashTable->pstBank);

    /*
 If allocation succeed, insert the new cell
*/

    if
(pstCell != orxNULL)
    {
      /*
 Set datas
*/

      pstCell->u32Key = _u32Key;
      pstCell->pData  = _pData;
      pstCell->pstNext = _pstHashTable->apstCell[u32Index];

      /*
 Insert it
*/

      _pstHashTable->apstCell[u32Index] = pstCell;
      eStatus = orxSTATUS_SUCCESS;

      /*
 Updates counter
*/

      _pstHashTable->u32Counter++;
    }


由于iarwain的注释存在,我更加没有必要说太多了。值得一提的是可以看到这里面甚至没有判断这个数组索引所在的位置是否已经被占,然后进行冲突处理的操作。
原因在于先
pstCell->pstNext = _pstHashTable->apstCell[u32Index];

然后
_pstHashTable->apstCell[u32Index] = pstCell;

假如原来此位置为空,next也就是空了,假如原来此位置有原来的值(甚至可以是一个链),就将整个链接在现在新数据的后面。

然后看set函数:

  /*
 Traverse to find the key
*/

  pstCell = _pstHashTable->apstCell[u32Index];
  while
(pstCell != orxNULL && pstCell->u32Key != _u32Key)
  {
    /*
 Try with next cell
*/

    pstCell = pstCell->pstNext;
  }

  /*
 Cell found ?
*/

  if
(pstCell != orxNULL)
  {
    /*
 Set associated datas
*/

    pstCell->pData = _pData;
  }
  else

  {
    /*
 Allocate a new cell if possible
*/

    pstCell = (orxHASHTABLE_CELL *)orxBank_Allocate(_pstHashTable->pstBank);

    /*
 If allocation succeed, insert the new cell
*/

    if
(pstCell != orxNULL)
    {
      /*
 Set datas
*/

      pstCell->u32Key = _u32Key;
      pstCell->pData  = _pData;
      pstCell->pstNext = _pstHashTable->apstCell[u32Index];

      /*
 Insert it
*/

      _pstHashTable->apstCell[u32Index] = pstCell;
    }
  }

  /*
 Done!
*/

  return
 orxSTATUS_SUCCESS;


用于在此数组位置进行进一步的索引,找到32位key完全一致的值。
  while
(pstCell != orxNULL && pstCell->u32Key != _u32Key)
  {
    /*
 Try with next cell
*/

    pstCell = pstCell->pstNext;
  }



 /*
 Cell found ?
*/

  if
(pstCell != orxNULL)
  {
    /*
 Set associated datas
*/

    pstCell->pData = _pData;
  }
  else

  {


.....
}

则完美回答了我博客中以前评论中的踢出来的问题,当hashTable 进行Set的时候,假如原来这个Key不存在会进行什么操作,很明显,不存在就Add。

 

到了这里,Get,Delete函数我感觉已经没有太多讲的必要了,只要对hashTable的概念还算熟悉的,对Orx的hashtable应该有足够的了解了,无论是实现的原理还是效率。

 

我小结一下:

1、Orx的HashTable定死了apstCell[orxHASHTABLE_KU32_INDEX_SIZE]这个数组的大小,orxHASHTABLE_KU32_INDEX_SIZE
等于256,所以,很自然的,此hashTable

在数量较小的时候才能保持较高的效率,(即HashTable的理论值O(1))假如数量远远超过256,那么查询效率几乎接近线性效率O(n)。

2、Orx这里的索引完全依赖于其String的32位CRC计算,但是很遗憾的,CRC的计算时会有冲突的,也就是会出现两个不同String但是计算出同样CRC的情况,我没有仔细了解这个CRC的计算,但是将整个HashTable的基础建立在这个不稳定的基石上,总的来说已经说明了这个HashTable存在问题。。。。。。。。。。。。。

 

Orx的HashTableKey是建立在String上的,为了提高String的比较效率,所以对String进行了CRC,效率是高了,但是引发了第2点说的问题。

但是这里其实不是没有解决方案的。

1。高效的方案:如暴雪的MPQ那样,不以一个CRC来决定一切,额外用2个来做保险,也就是进行3次不同的CRC运算,同时比较3个CRC都一样时才能确定一个真正的数据位置。此方法比Orx的方法多2次CRC计算,并且多2次整数比较,但是出错几率会比Orx的低的多。(在实际中,就算到几十万这个等级,我也没有碰到过3个CRC同时冲突)

2。而作为对比,C++中实现的hashTable(可以参考VS或者boost的hashMap实现),由于模板的存在,可以使用任何东西作为key,同时,在第一次快速索引时,使用一个hash值,当hash值冲突后,完全使用原来的key来进行比较,虽然假如是字符串的话会稍微慢一点,但是起码能保证绝对无错。

 

 

Tree

还是先看结构:


/*
* Tree node structure

 */

typedef
 struct
 __orxTREE_NODE_t
{
  struct
 __orxTREE_NODE_t *pstParent;           /*
*< Parent node pointer : 4
*/

  struct
 __orxTREE_NODE_t *pstChild;            /*
*< First child node pointer : 8
*/

  struct
 __orxTREE_NODE_t *pstSibling;          /*
*< Next sibling node pointer : 12
*/

  struct
 __orxTREE_t      *pstTree;             /*
*< Associated tree pointer : 16
*/

} orxTREE_NODE;

/*
* Tree structure

 */

typedef
 struct
 __orxTREE_t
{
  orxTREE_NODE *pstRoot;                        /*
*< Root node pointer : 4
*/

  orxU32        u32Counter;                     /*
*< Node counter : 8
*/

} orxTREE;

 

看到结构的时候,从list的诡异中我诡异的理解了这个Tree的意思,也就是,这个tree的用法也和list类似了。。。。。。。。。。。。

我虽然以前学习数据结构的时候是以伪码和C++的实现为主,没有看过太多C语言实现的数据结构,但还真不是完全没有见过,我感觉Orx的实现是很诡异的。。。。。。使用方式也是在算不上正统,为了求证,以防止是因为自己的孤陋寡闻引起的笑话,特意求证glib实现。。。。。。

比如GList


typedef
 struct
 _GList GList;

struct
 _GList
{
  gpointer data;
  GList *next;
  GList *prev;
};

一看到结构。。。。。。。。。。。。我大呼。。。。。数据结构的书籍还是没有骗我的啊。。。。。。。。。。。。。。。。。诡异的还是Orx。。。。。。。。。哪有需要自己一定要用一个新结构来包含Node来用一个list/tree的话。。。。。我要保存一个整数怎么办?。。。。。。。。简单的说,Orx的这个容器实现的非主流,不推荐大家使用及学习。。。。我这里也仅仅为了了解Orx的源代码而看看吧。。。。。。。。

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

Cocos2D For IPhone中的慢动作

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    慢动作是游戏中常用的特效手法,感觉Cocos2D这样完善的引擎应该原生支持才对,我在网上搜索了一下,竟然没有搜到,(不知道英文是不是该搜索Slow Action,但是原来公司的项目都这样叫的)。
    于是自己实现了一下,在自己项目的最底层的update中,添加一个slowAction的scale值,设定每次将update的参数ts传进来后,立刻乘以此scale值。只要原来的代码都是时间相关的,可以看到慢动作效果了。刚开始都还好,但是Cocos2D的粒子效果不吃这套,由于粒子系统不受我控制,我没法强行改变其值。本来还想通过全局变量强行改cocos2D的源代码的,在粒子系统的step函数中下了断点,然后通过callback stack中看到了sharedDirector调用了sharedScheduler的tick来实现cocos2d内部的update,并且很惊奇的发现了CCScheduler的timeScale变量。

于是,本文真正的非废话就下面一句:
[[CCScheduler sharedScheduler] setTimeScale:倍率];

同时,这也得原来的代码都是时间相关才能实现的,假如都是按帧走的,那么就哭去吧。。。。。。。。。。。。。所以说,很久很久以前,大家就开始认为时间相关的代码优于帧相关的代码不是没有道理的。。。。。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....