置顶随笔 #
2012年4月21日 #
深度优先搜索 --- stack
广度优先搜索 --- queue
Dijkstra --- priority queue
Dijkstra(A*) --- weighted priority queue
2012年3月19日 #
最近在工作中遇到一个小问题:设计一个小型的事件队列,用来解决技能释放,物品打造,采矿等事件执行时的延迟, 类似魔兽世界中法师放魔法前的读秒操作。 最开始, 我打算用一组抽象来解决这个问题:
class Event // 事件基类
{
public:
Event(int nInterval)
: m_nInterval(nInterval)
{}
virtual ~Event() {}
virtual void Execute(Actor* pActor) {}
int m_nInterval; // m_nInterval毫秒过后, 执行事件
};
以此为基类, 创建如下两个真正执行操作的类:
class CastSpellEvent // 释放魔法
: public Event
{
public:
CastSpellEvent(int nInterval, int nTarget, int nSpell)
: Event(nInterval)
, m_nTarget(nTarget)
, m_nSpell(nSpell)
{}
virtual void Execute(Actor* pActor)
{
pActor->CastSpell(m_nSpell, m_nTarget);
}
int m_nTarget; // 目标ID
int m_nSpell; // 魔法ID
};
class ForgeEvent // 物品打造
: public Event
{
public:
ForgeEvent(int nInterval, int nFormula)
: Event(nInterval)
, m_nFormula(nFormula)
{}
virtual void Execute(Actor* pActor)
{
int nItem, nNum; // 公式中的物品和物品数量
pActor->RemoveItem(nItem, nNum);
}
int m_nFormula; // 公式ID
};
基本工作搞定了, 但是还有一些小问题, 比如:如何获得这两个类的实例?如何销毁这些类的实例? 我是否需要用一个事件工厂来生产和销毁这些类的实例? 这会不会增加项目的复杂度? 其他人容易接受吗? 这些代码容易调试吗? 可扩展性高吗? 需要扩展性吗?............有些时候,程序员没有太多时间纠结完这些小问题, 那么让我们先看看如何使用这些类吧:
class EventQueue // 事件队列
{
public:
typedef std::vector<Event*> EventContainer;
public:
EventQueue(Actor* pActor)
: m_pOwner(pActor)
{}
public:
void Add(Event* pEvt) // 添加事件, 意味着玩家想搞点事情做。
{
if (pEvt)
m_Events.push_back(pEvt);
}
void Update(int nDelta)
{
// 更新,如果时间到了调用Execute
for ( ... )
{
m_Events[i]->Execute(m_pOwner);
// 后续处理
}
}
private:
EventContainer m_Events; // 事件队列
Actor* m_pOwner; // 事件队列的拥有者
};
class Actor // 我们的玩家
{
public:
Actor()
: m_EventQueue(this)
{}
public:
int CastSpell(int nSpell, int nTarget);
int RemoveItem(int nItemID, int num);
EventQueue& GetEventQueue() { return m_EventQueue; }
int Update(int nDelta)
{
m_EventQueue.Update(nDelta); // 每帧更新
}
private:
EventQueue m_EventQueue; // 只要给的起钱, 他(她,它)当然会得到一个事件队列。
};
int main() // 伟大的main函数
{
// 这些当然是随手乱写的,事件工厂什么的我懒得写了,你懂的。
Actor a;
Event* evt0 = new CastSpellEvent(10, 1, 1);
Event* evt1 = new ForgeEvent(100, 1);
a.GetEventQueue().Add(evt0);
a.GetEventQueue().Add(evt1);
a.Update(100);
return 0;
}
采用抽象的方式, 必然导致new的存在, 这可能并不一定正确, 但我在大部分情况下都会编写出类似代码。 依然有一些小问题需要考虑, 比如: 这样做效率高吗? 会对内存池造成压力吗? 会产生内存碎片吗? 容易调试吗? 其他人容易接受吗? 会增加复杂度吗? ...........纠结完这些问题后, 工作告一段落。
不过事情到这里还没有完, 因为我并不认为这是一个非常不错的解决方案, 除了上面提出的各种问题外, 还因为这里有设计模式存在, 对于这样一个简单的问题, 设计模式显得太过臃肿, 对于内存池来说, 内存分配过于频繁。 从成本的角度考虑, 这样的做法代价有些高了。 那么有没有其他解决方案? 让我想想................
(这些代码都是实际项目代码的简化版, 真正的代码要复杂的多, 复杂到让人一夜之间白发三千丈。)
2012年2月13日 #
这可能是我辈必经之路吧......
添加一条ctags命令: ctags.exe -R --extra=+fq --sort=yes --fields=+afiKSs --c++-kinds=+px
2012年1月30日 #
2012年1月22日 #
很长时间没有再看过lua,最近快过年了,我终于有机会再写一些lua的心得体会。大约半年前,一个同窗好友看了我的lua学习笔记,跟我道谢,表示学到了不少东西。我赶紧跟他解释,这些学习笔记都是我自学时乱写的,有很多错误的用法,应当用来当作前车之鉴,而不是学习材料。
最开始学习lua的时候,我太执着于将C++的一些东西强加给lua,比如将C++类的成员函数导出给lua,以及让C++可以透明的操作lua中的table,metatable等。现在想想,这些东西是完全没有必要的。
lua不是C++,C++也不是lua,他们的编程思想有很大差别,使用方式也有很多区别。C++和lua的交集体现在函数调用上,比如C++调用lua编写的某个函数,lua调用C++编写的某个全局函数。这里要注意的是,lua调用的是C++的全局函数,或者说就是纯C函数,更具体一些就是形如int callback(luastate*)的函数,而且只能调用这种签名的函数。只有将函数地址保存为callback函数的upvalue,才能将成员函数注册进去,当lua调用C++的callback函数时,再将upvalue解析出来。这种做法比单纯的全局函数注册复杂,而且不见得实用,如果对某个实例调用其类的成员函数,不如从lua中传入一个userdata或者lightuserdata到C++中,再进行调用。那么lua是怎么得到这个userdata的呢?可以有很多种方式,比如C++调用lua函数的时候传入userdata等。
lua的表操作也不适合放在C++中进行,例如设置metatable这样的操作,最好的办法是把这些操作放到lua脚本文件中。虽然在C++中做和在lua中做本质是一样的,但是C++很不擅长做这些琐碎的事情,比如构建一个非常复杂的table,其中还有table的嵌套,各种metatable的设置等。在C++中过多的编写对lua自身特性的操作,会让程序变得复杂且难以调试。当然,C++可以利用table保存一些必要的信息,比如调试需要用的调用栈信息等等,不过最好不要有复杂的操作。
总的来讲,应当尽量简化C++和lua之间的调用操作,并降低两者之间对对方特性的依赖,同时,让C++和lua做自己最擅长的事情,从而达到事半功倍的效果。
祝大家新年快乐!!!
2012年1月20日 #
2012年1月4日 #
2011年12月24日 #
2011年12月16日 #
2011年12月15日 #

