Ogre LogManager 分析
设计上从名字就可以推断出来LogManager使用了单例模式,这样为LogManager的全局访问提供了非常大的帮助。
下面是LogManager的继承结构:
继承结构:
Ogre::Singleton<LogManager>:
我们之前有对Ogre的Singleton模式进行过分析,这里不再赘述。详见 Ogre 设计模式之Singleton
LogAlloc:
本来我想把LogAlloc附带分析了,但是分析代码的过程中,发现其内部的复杂度已经超过我目前的C++和多线程的理解水平,所以未来抽时间单独的把LogAlloc分析一下。
现在先把LogAlloc概述一下,希望大牛们看到这篇文章可以指点一二。
LogAlloc是GeneralAllocatedObject的别名,而GeneralAllocatedObject 是 AllocatedObject的一种通用分配策略(GeneralAllocPolicy),这种采基于Policy的设计,在 Modern C++ Design 里面有详细的讲解。
AllocatedObject 中重载了 new , new[], delete , delete[] 等诸多方法,我们会看到new的很多重载版本,我们这里讲解一个:
void* operator new[] ( size_t sz, const char* file, int line, const char* func ) { return Alloc::allocateBytes(sz, file, line, func); }
讲到这里不得不先提一下Ogre LogManager的初始化方式,如下:
mLogManager = OGRE_NEW LogManager(); mLogManager->createLog(logFileName, true, true);
和Ogre的OGRE_NEW 宏,如下:
# define OGRE_NEW new (__FILE__, __LINE__, __FUNCTION__)
OGRE_NEW 的 __FILE__, __LINE__, __FUNCTION__ (这三个参数是编译器内部定义的宏,记录出错的文件,行和函数) 三个参数在 调用 OGRE_NEW XXX[] 时传入 operator new。
这样做的目的是可以为了定位错误所用,当new失败的时候,需要throw的时候,Ogre会把抛出的错误,以写入日志或以弹出窗口的方式来告知用户。
参数是Alloc是模板参数,这里的具体参数是 GeneralAllocPolicy ,它是CategorisedAllocPolicy<Ogre::MEMCATEGORY_GENERAL> 的别名,而 CategorisedAllocPolicy 定义如下:
template <MemoryCategory Cat> class CategorisedAllocPolicy : public NedPoolingPolicy{};
继承的基类 NedPoolingPolicy 采用了 nedmalloc 的方式,它是多线程的,带锁,其实nedmalloc 也是线程缓存式的内存池。nedmalloc 的确没有看懂,希望大牛指点一二。
这里的模板参数要注意,它是enum类型的,指示需要制定的内存分配策略。
好了 LogAlloc 就介绍到这里,关于Ogre的内存分配,我会以后花时间详细分析一下。
LogManager的成员和方法:
回到LogManager的主题(囧 不知不觉感觉刚才有点跑题),LogManager有很多辅助方法如 getter/setter ,这种不再赘述,重点讲解一下LogManager的以下三种方法:
1. Log* createLog();
2. void destroyLog();
3. void logMessage();
1. createLog() 方法:
Log* LogManager::createLog( const String& name, bool defaultLog, bool debuggerOutput, bool suppressFileOutput) { OGRE_LOCK_AUTO_MUTEX Log* newLog = OGRE_NEW Log(name, debuggerOutput, suppressFileOutput); if( !mDefaultLog || defaultLog ) { mDefaultLog = newLog; } mLogs.insert( LogList::value_type( name, newLog ) ); return newLog; }
在LogManager中的方法几乎都要加锁,createLog也不例外。这里使用了Ogre使用了Ogre独特的加锁方式,这种加锁方式很好的避免了重入和加锁之后忘记解锁的缺点。内部的实现已经类似于局部的类,开始的时候构造加锁,最后离开函数析构解锁。
因为可以Ogre允许创建多个log(默认只有一个),创建之后插入mLogs,它是一个LogList,LogList的定义如下:
typedef map<String, Log*>::type LogList;
使用map的原因在于想把key和value对应起来,这个用处在 destroy() 方法中将会有体现。最后这个方法新创建的Log指针。
2. void destroyLog(); 方法
destroyLog() 重载了两种方式,第一种是通过string,另一种是通过Log指针。但是第二种是通过获得Log指针的name,来调用第一种,所以本质上说是一种方法。
void LogManager::destroyLog(const String& name) { LogList::iterator i = mLogs.find(name); if (i != mLogs.end()) { if (mDefaultLog == i->second) { mDefaultLog = 0; } OGRE_DELETE i->second; mLogs.erase(i); } // Set another default log if this one removed if (!mDefaultLog && !mLogs.empty()) { mDefaultLog = mLogs.begin()->second; } }
简单明了,查找和字符串相同名字的log,然后用OGRE_DELETE删除掉,如果是删除当前默认的Log,那么选择删除后map的第一个作为默认的log。
3. void logMessage(); 方法
这个是LogManager里面最重要的方法,方法中调用了mLog成员变量的logMessage
方法,这样做主要是把log的具体实现,主要是文件操作,和manager分离开来,达到manager是独立的效果。
在这个方法中需要注意的有以下三点:
A) 关于log报告的等级,如下代码将会根据你需要的等级对log进行写入。
if ((mLogLevel + lml) >= OGRE_LOG_THRESHOLD)
B) 这里log的实现使用了观察者模式,如果需要监听log 就需要继承 LogListener,并实现抽象方法 messageLogged() ,然后调用addListner()方法把需要监听的类的指针注册进来,存放在一个vector里面,当每次需要写入log的时候,logMessage()方法就遍历vector,调用对应监听者的messageLogged() 的方法。
C) Ogre会根据你的需要写入控制台还是file中,而且每次是即时写,即输出到对应目标,调用内嵌类的 Stream (本质是个 basic_string )的对象 mLog 的 flush() 方法。
好了,今天LogManager就分析到这里,下一次分析Ogre SDK 的一个例子或者 Ogre 的 DyLibManager。