C++ C++;:游戏引擎的事件系统实现

C++ C++;:游戏引擎的事件系统实现,c++,events,game-engine,C++,Events,Game Engine,我正在计划一个事件驱动的游戏引擎。其基本思想是,不是让所有的东西都与所有的东西对话,而是所有的东西都与事件系统对话,事件系统将消息转发给它们的接收者,而无需将接收者耦合到通知者或其他方式 对象将自己注册到事件系统。通知ID和回调函数指针作为每个注册命令的参数传递 对象向事件系统添加通知。通知的ID作为每个通知的参数传递。通知将添加到包含所有挂起通知的队列中 此外,事件系统支持计划通知。通知ID和未来执行时间作为每个通知的参数传递。然后,计划通知存储在数据结构(“计划”)中,该数据结构按未来执行

我正在计划一个事件驱动的游戏引擎。其基本思想是,不是让所有的东西都与所有的东西对话,而是所有的东西都与事件系统对话,事件系统将消息转发给它们的接收者,而无需将接收者耦合到通知者或其他方式

  • 对象将自己注册到事件系统。通知ID和回调函数指针作为每个注册命令的参数传递
  • 对象向事件系统添加通知。通知的ID作为每个通知的参数传递。通知将添加到包含所有挂起通知的队列中
  • 此外,事件系统支持计划通知。通知ID和未来执行时间作为每个通知的参数传递。然后,计划通知存储在数据结构(“计划”)中,该数据结构按未来执行时间的顺序保存计划通知
  • 调用器对象命令事件系统处理所有排队通知。事件系统按顺序获取通知,并调用已使用与当前通知相同的ID注册自身的每个对象的回调函数
  • 调用程序对象命令事件系统处理一个计划通知。从计划中获取最早的通知,并调用使用相同通知ID注册的所有对象的回调

班级注册
{
公众:
void callback(void){callback_u();}
void setCallback((*callback)(void));
void addToEventSystem(int-ID、EventSystem和EventSystem);
私人:
作废(*作废)(作废);
};
类事件系统
{
公众:
无效登记(int ID,登记*登记);
作废注销(int ID,注册*注册);
void addNotificationToQueue(int ID);
void addNotificationToSchedule(int ID,int notificationTime);
作废处理队列通知(作废);
作废流程下一个计划(作废);
int getCurrentTime(无效);
私人:
//占位符类型
通知队列;
通知时间表;
};
//------------使用:------------------
类接收方对象
{
公众:
空隙度凝灰岩(空隙);
void初始化(void){
按键注册。设置回调(doStuff);
//可能对同一eventsystem进行多个ID不同的注册
按键注册。添加到eventSystem(1234,eventSystem);
添加到eventSystem(42,eventSystem);};
私人:
注册按键注册;
};
int main()
{
破产管理人客体破产管理人客体;
事件系统事件系统;
receiverObject.initialize();
eventSystem.addNotificationToQueue(1234);
eventSystem.processQueuedNotifications();
}

然而,我对这个解决方案并不完全满意,主要是因为系统不允许轻松地将参数传递给接收者,而且我对成员函数的回调表示怀疑,这是一个好的设计实践吗?我想到的方法/类/变量名是什么?欢迎建设性的批评、指导和解决问题的其他方法。

我个人会有一个接口类
EventHandler
,您可以注册它,它将有一个虚拟的
操作
函数(可能还有一个用于事件系统告诉类它正在被注销的函数)。这完全避免了回调,
EventHandler
的实际实现将有可能保存其他数据(或对其他数据的引用)

这与您的问题没有严格的关系,但说到设计,我会避免使用“一切”的全局通知系统,因为我在过去看到了坏的后果。在一个对象只调用另一个对象上的某个方法的地方,您将倾向于使用重事件系统

专门的模板化系统工作得更好,即允许您控制对象寿命并设计用于处理其中特定类型和已知参数的事件的系统


在任何情况下,您都会发现很难解决一些问题,例如等待传递到已被杀死的收件人的挂起事件。

对于参数问题,您可以定义派生为模板的基类ParamList:

class ParamList
{
     const ID_Type& GetParamsType();
     ...
};
template <typename T>
class Params: public ParamList
{
     ...
}
类参数列表
{
const ID_Type&GetParamsType();
...
};
模板
类参数:公共参数列表
{
...
}
现在回调的类型为: 无效(*回调)(参数列表&); 为了安全使用,您可以添加到类注册中 const ID_Type&GetExpectedParamsType(); 在回电话之前使用它

sames会发送通知: void addNotificationToQueue(int-ID、ParamList和param);
void addNotificationToSchedule(int-ID、ParamList和param、int-notificationTime)

为回调传递一个
std::function
而不是一个普通的函数指针怎么样?确实,这些看起来很有希望。我只是没有c++11方面的经验和知识,但我会给新标准一个外观,比我目前的回调想法更有意义,但是参数/数据传输呢?默认情况下,
EventHandler
的具体实现必须知道receiver类,这是可以的,但是如果要传输数据,它还必须知道数据的来源(可能是首先将事件添加到
EventSystem
的类),我不确定你的意思-在我看来,receiver类是
EventHandler
的一个实例。我认为你在最初的回答中的意思是
Registration
已经成为一个名为
EventHandler
的抽象基类,而
注册
/
事件处理程序
中的回调系统已被实现所需功能的派生类使用的虚拟函数所取代
class ParamList
{
     const ID_Type& GetParamsType();
     ...
};
template <typename T>
class Params: public ParamList
{
     ...
}