ACG狂人

其实我更爱姐汁...

2011年5月6日 #

boost::asio网络传输错误码的一些实验结果(recv error_code)

错误码很重要,可以由此判断网络连接到底发生了神马事情,从而驱动高层逻辑的行为。只有笼统的错误码判断的网络层是不够规范的,鄙人觉得有些错误码还是需要在网络层就区分开的,特此记录一些当前实验的错误码以及发生原因。

以下是一部分在async_receive()的handler处捕获到的比较有用的错误码
错误码(十进制) 枚举 发现原因
10009 boost::asio::error::bad_descriptor 在一个已经关闭了的套接字上执行async_receive()
995 boost::asio::error::operation_aborted 正在async_receive()异步任务等待时,本端关闭套接字
10054 boost::asio::error::connection_reset 正在async_receive()异步任务等待时,远端的TCP协议层发送RESET终止链接,暴力关闭套接字。常常发生于远端进程强制关闭时,操作系统释放套接字资源。
2 boost::asio::error::eof 正在async_receive()异步任务等待时,远端关闭套接字,这里跟10054发生的情况似乎一样,但是实际上应该是有区别的,具体神马区别,由回复中jack的说法,这个是远端正常关闭套接字。
只是一些浅陋的测试,目前觉得有用的也就是这几个,不正确的地方请送我鸡蛋。

posted @ 2011-05-06 18:06 酿妹汁 阅读(12793) | 评论 (5)编辑 收藏

2011年1月30日 #

boost的bjam编译指令

前面必须使用到的,类似下面的指令
F:\sdk\boost>bjam
--link=static --threading=multi --runtime-link=shared debug release stage

后面需要选择编译器和要编译的库
--toolset=msvc-9.0 --with-date_time --with-thread......

posted @ 2011-01-30 09:34 酿妹汁 阅读(536) | 评论 (0)编辑 收藏

2010年12月29日 #

OGRE与MFC的文件系统冲突问题

这两个东西在一起问题真呀么多......前些日子才写的一个注意事项的随笔,这回又有问题需要记录,好吧,开新文写。
问题:由于项目的复杂度,问题的表现与原因其实相差十万八千里。
原因:MFC在打开和保持文件后(即打开CFileDialog对话框后),就会修改进程的当前目录,就是SetCurrentDirectoy(),导致OGRE里那些用相对路径做Location的资源目录下的文件全部无法读取(其实我觉得OGRE应该把这些相对目录在读取文件的时候换成绝对目录)。具体就是openResource()中调用stat()出错,文件系统中找不到指定文件。
解决方法:在合适的地方调用SetCurrentDirectoy()把进程当前目录设置回进程工作目录吧......

于是又是一下午+半个晚上的调试时间......

posted @ 2010-12-29 20:49 酿妹汁 阅读(1820) | 评论 (5)编辑 收藏

2010年12月23日 #

备忘随笔系列2:内存错误

接上文《备忘随笔系列1:MFC与OGRE联姻注意事项》之后,再记录一下内存错误,经过无数次莫名其妙的内存问题之后,发现一些找不着北的内存Crash问题出现的原因都很荒谬,所以本篇主要例举一下近期出现的一些怪异内存问题和让人啼笑皆非的原因所在。

问题1:编译器在编译那些访问成员变量的代码时算错了相对于this指针的偏移字节数;赋值给下面一个变量时,却修改了上面一个变量的值。
原因:与我共事的某位大仙由于酷爱使用结构体传递网络包,所以在某头文件里用#pragma pack(1)包括住了整个头文件,一不小心把#include "其他头文件"那些行也给包括了进去,其中不乏Windows.h  stl云云......
解决办法:当然那个啥......把#pragma pack(1)的位置往下去几行,还是细心点吧...浪费了整整一天调试。

问题2:从网络另一端机器发过来一个结构体,分别接收一个结构体中的多个数据成员和一次性接收整个结构体取出的数据不同。
原因:这是个很2的情形,两个相同的结构体分别在不同的头文件中,且一个有#pragma pack(1),一个没有。
解决办法:如果要用结构体传递网络包,还是共用头文件吧......

其实......很多内存问题很不好描述,我也不经常出现如上那样纠结的问题,所以下面我还是说一个最常见的内存问题(0x.....地址访问冲突)和原因吧:
“0x.....地址访问冲突”这个Crash基本上每个人都遇到,而且经常遇到,但是大部分都很容易解决。判断问题的原因可以看这几点:
原因1:如果0x....这个值很小,一般就比0大一些,而且是在访问某对象中的数据成员时出错的,那么这基本都是因为该对象指针为空,你用了空对象指针调用了代码。
原因2:如果0x...值同样很小,但是并非在访问某对象中的数据成员时出错,而是调用某函数那一行时出错的,那么这个函数十有八九是个虚函数,如果我说中的话,那原因应该如前面的原因1相同,只是这回是读取虚函数表时就崩了。
原因3:如果0x...值类似是0xcdcdcdcd和0xeeeccc或者与这相近的数,且同样是在访问数据成员或调用虚函数的时候出的问题,那么这就算是个野指针问题了,释放了就别再用啊。
原因4:内存越界,这个对程序造成的麻烦比任何麻烦都要大,但是问题并不隐蔽,记得为每个类的数据成员进行必要的初始化。
原因5:使用了memset或ZeroMemory清空一些对象或对象数组。特别是对象数组,很容易让人忽略这个问题。有些程序员会觉得某对象里都是可以这样清空的数据成员,所以便这样做了,但是往往虚函数表指针会被忽略,这个指针绝对不能一起被清空的。
总结:不要让表达索引的整形在初始化后是个未知值;不要让指针没有在初始化时被赋0值;不要不检查指针的值就拿它访问成员函数和成员数据;不要重复释放指针所指对象;不要使用释放后和未初始化的内存数据;可以的话使用智能指针;释放指针所指地址后,为指针赋0值;只有在完全是内部类型构成且没有多态的类型对象上使用memset为对象赋值。

posted @ 2010-12-23 23:41 酿妹汁 阅读(1773) | 评论 (3)编辑 收藏

备忘随笔系列1:MFC与OGRE联姻注意事项

细节决定那啥来着,一些细节虽然不是什么难事,但是一旦卡住总是会很烦心,需要太多时间去调试,耽误的是宝贵的项目进度,所以我将在这里把一些总结贴出来,愿能给国内的游戏技术圈同僚们一点小帮助,节约宝贵的时间,毕竟总是在网络上摄取营养,算是回报社会吧。

本文记录最近发现的一些 MFC 和 OGRE1.7.2版本 联姻的注意事项:

问题1:创建Ogre的CView窗口后,无法截获鼠标点击和移动信息,只能获取鼠标滚轮信息。
原因及解决方案:传递CView窗口句柄时,请一定使用externedWindowHandle的属性key,切记不要使用parentWindowHandle,因为parentWindowHandle是让CView成为渲染窗口的父窗口,鼠标键盘消息都不会路由到CView上,而是在渲染窗口里被截获;而externedWindowHandle是让CView窗口本身成为渲染窗口,所以CView才能正常截获到输入消息。

问题2:当解决问题1之后,发现使用externedWindowHandle绘制出的窗口很小,而使用parentWindowHandle时则正常
原因及解决方案:注意继承CView::OnSize()函数响应WM_SIZE消息,但请切记:千万别在OnSize中调用Ogre::RenderWindow::resize()函数,这会导致OnSize()函数的递归回调,因为Ogre::RenderWindow::resize()函数中会调用AdjustWindow()和SetWindowPos()函数,这会导致发送WM_SIZE消息并缩小窗口,从而导致问题的发生。

问题3:如何解决窗口重置大小的问题
解决方案:在OnSize()中不能调用Ogre::RenderWindow::resize()函数,而应该调用Ogre::RenderWindow::windowMovedOrResized()函数,通知RenderWindow在渲染前重新设置Viewport的宽高比例。

问题4:怎样确保主渲染循环
分析:上网看了一些相关的解决方案,发现大多使用WM_TIMER消息来维持OGRE的主渲染循环,这应该是下下策的方案了吧......当然还有其他的实现方案,譬如开另一个线程,这个方法还是可行的,但是总有些不对味,因为渲染明明应该在主线程中才是最佳方案。于是我就看了一下MFC闲下来的时候都干了些什么,最后发现了以下解决方案,应该算是很不错但并不难的解决办法了,为什么没见网上有人提供这样的方案让我很不理解,窝着藏着也得不到半点好处:
解决方案:使用空闲回调。该回调是需要继承CWinApp::OnIdle()函数(好像是叫这个,反正肯定带Idle这个单词),当主线程中的消息循环没有取到消息时(调用PeekMessage()没有获取到消息),就会去调用这个函数,于是......就在这个函数里调用绘制一帧吧:Ogre::RenderWindow::update(),另外有动画的话还需要调用Ogre::Root::_fireFrameRenderingQueued(),因为动画更新在这里。如果是想让所有渲染对象都更新一帧的话,直接调用Ogre::Root::renderOneFrame()吧。

解决方案不一定最好,也不一定适合你的情况,但愿能尽微薄之力,也是作为我个人的备忘吧。

posted @ 2010-12-23 01:39 酿妹汁 阅读(2345) | 评论 (6)编辑 收藏

2010年11月20日 #

关于MVC PropertySet OperatorStack的一些设计思考

最近在给公司里码一个场景编辑器,大致得实现的功能有:
地形高度刷
地形纹理刷
放置小物件和房屋
放置粒子系统
设置路径点和只能摄像机点

算是个简单的不能再简单的场景编辑器了吧...但是这样的一个工具还是很头痛的,特别是用C++来写...
头痛的原因不是别的,正是这个表现层和后台数据同步问题。这个在C++的UI库中目前还真没有什么现成的好办法,于是开始造轮子,为MFC写了PropertySet和OperatorStack。
首先这个UI数据和内存数据双向同步的问题直接让我崩溃了...由于以前写过一些工具,知道这东西如果不做个设计就开始冲着功能写的话会有什么后果。嗯,于是继承封装了CMFCPropertyGridCtrl控件,为每个叶子属性项封装了一个LeafItem,根据属性名来更新PropertySet里对应的数据......具体实现几千字略- - 最终成型时代码这样:
DynamicObject obj;
propertyGrid.attachObject(obj);
这里的DynamicObject继承PropertySet,于是propertyGrid控件就会显示obj里所有的属性数据了...然后是双向更新问题,目前是给Property里加了一个eventValueChanged事件响应,让PropertyGridCtrl监听这些数据的变化,而propertyGridCtrl这个UI上的数据变化同样是派生实现CMFCPropertyGridCtrl的值变化响应函数来给绑定的LeafItem更新数据,也是直接就刷新到Property里了。
还有OperatorStack.....这个是操作栈,记录用户操作的,用于撤销和重做的操作,也用到了PropertySet来记录变化对象的属性快照,嗯,叫SnapShootRecord的类里面记录的都是一个对象的变化属性。
先就记录这么多,很乱很不容易懂,主要给我自己做个记录的,没啥贡献,实际上还有很多不好用的地方,所以最近在想一些改进设计,等我想好了放上来详细设计和源码吧.......

posted @ 2010-11-20 19:30 酿妹汁 阅读(1769) | 评论 (2)编辑 收藏

2010年10月12日 #

终于完成了自己的模板设计,初步实现了filter_streambuf,cge项目启动......

实现的目的是为了在一些特定情况下不去使用boost的filter_streambuf,不使用boost::iostreams的理由如下:
1、基于运行时配置的过滤器,效率稍低
2、对于网络通讯而言,boost的filter_streambuf乃至整个iostreams库都显得较为臃肿。
所以,我自己编写了一套filter_streambuf,继承了std::streambuf,并配合自己重新设计的archive和batch_data进行网络通讯,无论是效率还是易用性上都超出使用boost的iostreams。而boost的那套东西经过我的反复使用后,觉得更适合用在文件读写和数据持久化上。
如果要说哪里不如boost的filter_stream,也就是boost的filter_streambuf可以动态配置filter,而我使用的是模板技术将filter的关系在编译期就关联了起来,所以只能是静态配置filter。下面是具体使用时的完整例子代码:
 1 #include <ccs/util/ios/ifilter_streambuf.hpp>
 2 #include <ccs/util/ios/ofilter_streambuf.hpp>
 3 #include <ccs/util/ios/memory_terminal.hpp>
 4 
 5 using namespace ccs;
 6 using namespace util;
 7 
 8 // 输出过滤
 9 struct my_ofilter
10 {
11     typedef ios::ofilter_tag tag_type;
12 
13     template<typename OutT>
14     std::streamsize write(const char* p, std::streamsize n, OutT& _out)
15     {
16         std::streamsize i = 0;
17         for (; i < n; ++i)
18         {
19             char c = p[i];
20             if (_out.write(&++c, 1!= 1)
21                 break;
22         }
23         return i;
24     }
25 };
26 
27 // 输入过滤
28 struct my_ifilter
29 {
30     typedef ios::ifilter_tag tag_type;
31 
32     template<typename InT>
33     std::streamsize read(char* p, std::streamsize n, InT& _in)
34     {
35         std::streamsize i = 0;
36         for (; i < n; ++i)
37         {
38             char c;
39             if (_in.read(&c, 1!= 1)
40                 break;
41             p[i] = --c;
42         }
43         return i;
44     }
45 };
46 
47 // 输出内存设备
48 struct memory_odevice
49 {
50     typedef ios::dest_tag tag_type;
51 
52     std::streamsize write(const char* p, std::streamsize n, ios::memory_oterminal& _out)
53     {
54         return _out.write(p, n);
55     }
56 };
57 
58 // 输入内存设备
59 struct memory_idevice
60 {
61     typedef ios::source_tag tag_type;
62 
63     std::streamsize read(char* p, std::streamsize n, ios::memory_iterminal& _in)
64     {
65         return _in.read(p, n);
66     }
67 };
68 
69 
70 int main(int _Argc, char** _Args)
71 {
72     char buf[256];
73     ios::memory_oterminal memout(buf, 256);
74     ios::memory_iterminal memin(buf, 256);
75     ios::ifilter_streambuf<ios::memory_iterminal, mpl::list2<my_ifilter, memory_idevice> > insbuf(&memin);
76     ios::ofilter_streambuf<ios::memory_oterminal, mpl::list2<my_ofilter, memory_odevice> > outsbuf(&memout);
77     std::istream is(&insbuf);
78     std::ostream os(&outsbuf);
79 
80     int num = 188;
81     os.write((char*)&num, sizeof(int));
82     os.flush();
83     is.read((char*)&num, sizeof(int));
84 
85     std::cout << num << std::endl;
86     system("pause");
87 }

代码中的意思就是将写入的数据逐字节的加1,并保存在内存缓冲里,然后又从内存缓冲中读出,逐字节减1,并输出到控制台,一套经过过滤的读写流便完成了。由于使用了模板元的list作为链接,在release模式下所有的过滤器操作都是内联的,这虽然也是我预想的效果,但看完汇编码之后,着实让我高兴了一晚上,这种成就感真的是programer最大的乐趣。

需要说明的是:代码中的mpl::list2是自己实现的模板元链表...过段时间考虑研究一下boost的并替换过来,因为那个list后面的2让我觉得很不够智能...当然,如果boost的list实现过于复杂,或是不能让我的代码完全内联化的话,肯定不会考虑使用。

完成这个之后,我便准备着手构建cge项目,所谓的cge,就是cloud game engine的缩写...顾名思义就是使用了云技术的游戏引擎,我想在业余时间尝试一些颠覆传统cs架构的在线游戏引擎架构设计,具体难点估计会有2个:
1、运用gpgpu group的并行运算技术,考虑使用目前市场占用率最大的nvidia tesla服务器配合cuda,在服务器用physX实现一定的物理模拟。
2、在即时性较强的在线游戏中,ping值一直是最大的挑战,所以有选择性的使用云计算技术,这是架构设计上的挑战。
关于cge的设计思考和规划,会另外开贴具体阐述,并记录开发进度和情况。

posted @ 2010-10-12 19:37 酿妹汁 阅读(2652) | 评论 (4)编辑 收藏

用cmake生成ogre1.7rc的项目文件,哇擦泪......

记录一下这个,容易忘记的DXSDK_DIR环境变量,可以在cmake里添加dx的sdk路径,否则找死也找不到rendersystem_direct3d的项目文件。

posted @ 2010-10-12 18:59 酿妹汁 阅读(311) | 评论 (0)编辑 收藏

2010年7月1日 #

析构过程中内存相关错误的绝大多数原因

今天记录一下长久以来屡次犯的错,每次都是换一种方法编码来绕过这个问题实现功能的,因为这个问题太过隐蔽,导致今天才发现其中真正的原因...下面进行问题描述:
1std::map<std::string, Value> keyValue; // 在函数内部分配的堆栈对象(局部变量)
2ReadData(keyValue);// 从dll中导出的函数
3keyValue.clear(); // delete中出现assert异常

第一行是在应用程序中的堆栈中分配的内存空间。
第二行是我自己写的dll库,用来读取一些数据加入到keyValue中。
第三行是清空keyValue,其实如果不写这一行的话,keyValue也会在函数结尾时清空,到那时同样会出现错误。
这一切乍一看没啥问题,keyValue是局部变量,为什么局部变量的释放会出现异常错误呢?这是因为第二行ReadData的缘故。ReadData的逻辑在另外一个可执行模块中,在其中分配的内存空间不一定与当前模块在同一个堆区。
我们知道,std::map是一个树结构的容器,我在ReadData内部往keyValue中添加了数据,keyValue中会在堆区中分配树节点,而这个节点将会在当前模块在keyValue的析构中被释放。也就是说,我无意中在dll模块中分配了堆空间,又无意中在exe模块中企图释放该空间,这样的行为导致错误是不足为怪的。
时刻牢记,在一个模块中分配和释放同一块内存区域,警惕你所看不见的内存分配和释放。

posted @ 2010-07-01 15:47 酿妹汁 阅读(2626) | 评论 (11)编辑 收藏

2010年4月24日 #

完成的网络数据包文档化

好久没写blog了,这次初步完成了一个文档化的网络流框架,这玩意儿是咱自己这样叫,但具体是啥玩意儿呢?其实就是将网络通讯数据结构给串行化到缓冲里,再发送到网络的另一端,由另一端再串行化到相应的类型对象里。恩,这听起来没啥难度呀,但事实并非如此,呵呵,该架构建立在asio基础之上,目前完成了tcp通讯部分,基本可以很方便的使用了。
        为啥我要写这么个架子,因为网络通讯需要考虑很多情况,如粘包、未接收完整、缓冲不够大等情况,而且在项目开发过程中,不断的添加和修改一些通信协议相关的数据包结构。为了让程序员不要管那么多麻烦的情况,同时易于修改和添加新的通讯协议,于是就写了这么个架构,不过今天比较忙,还是下次传上用例代码吧,源码可能会在不久以后发布的通用库模板库里找到。
恩,咱要发布自己的一个开源库,建立在stl和boost基础上,可跨平台编译 0 0......
到时候再说了。

posted @ 2010-04-24 20:34 酿妹汁 阅读(450) | 评论 (0)编辑 收藏

仅列出标题  下一页