原生C++中的数据驱动设计

Data Driven Design in native C++

本文关键字:数据驱动 C++ 原生      更新时间:2023-10-16

所以我正在编写一个游戏引擎,它利用数据驱动的设计来实例化xml文件中的各种Actor。 我最近完成了事件管理系统的编码,所有内容都会触发事件,处理程序会正确捕获它们。 考虑到我使用了成员函数指针和模板,这不是一件容易的事。 我现在遇到的问题是我希望能够在 xml 文件中做这样的事情:

<InputComponent>
   <Event  EventName="GainedFocus" OnEvent="Initialize"/>
</InputComponent>

问题应该很明显。C++没有形式的反射,因此据我所知,无法从字符串"初始化"中获取 Initialize 函数的地址。 这意味着对于略有不同的输入组件,我从类派生并注册特定于该实例的事件并添加函数。 另外,应该知道这与我处理典型输入的方式不同:

<InputComponent>
   <Mapping Type="MousePress" Button=1 Command="RotateCamera"/>
</InputComponent>

在此实例中,映射是特定于输入组件的元素,命令本身是使用工厂创建的对象。 不过,我无法真正对通用事件执行此操作。 命令是对不同对象执行完全相同操作的特定项,而对象或组件本身通常需要以不同的方式处理单个事件。 以前有人做过这样的事情吗? 真的很好奇有人如何去做这样的事情,以便需要注册到对象的事件不必硬编码到类本身中。

编辑:让我这样说。 相同类型组件的不同实例需要对事件做出不同的反应。 这将是特定于应用程序的,因此应该与引擎代码分开(我不应该修改组件)。 用户应该能够提供函数,然后可以在对事件做出反应时调用这些函数。 函数和事件都应该能够在 XML 中绑定。 我开始认为这在非托管C++中可能是不可能的,因为没有形式的元数据来查找用户基于同名字符串提供的函数。

可以使用将字符串与函数指针相关联的映射。
或者,如果函数签名不同,则可以使用带有 if-else-if 梯形图的工厂模式。

编辑 1:示例

// Typedef for event functions
typedef void (*Event_Function_Pointer)(const Event& e);
typedef std::map<std::string, Event_Function_Pointer> Event_Function_Container;
//...
Event_Function_Container events;
events["Gained Focus"] = Initialize;

还有一个选项是查找表,因为文本和函数指针是常量数据:

struct Event_Function_Entry
{
  char * const * event_name;
  Event_Function_Pointer event_function;
};
Event_Function_Entry events[] =
{
  {"Gained Focus", Initialize},
};

您的组件都可以从同一个基继承,该基库为命令将调用的基本操作提供了接口:

class Component {
public:  
    virtual void initialize(); 
    virtual void rotate(int x, int y); 
    ...
};
class Monster : public Component {
   virtual void initialize();   // concrete implementation for a Monster 
   virtual void rotate(int x, int y); 
};

然后,您可以考虑"命令"设计模式。 一般思路是:

class Command {   // generic interface for commands
    Component *receiver;  
public: 
    Command(Component *receiver);  
    virtual ~Command(); 
    virtual void execute();     
};
class InitializeCommand : Command {  // a specific command
public: 
    InitializeCommand (Component *receiver /* + additional command specific parameters */); // specific constructor with all needed parameters
    void execute() { 
         // use the parameters and do the common operations 
         receiver->initialize();   // polymorphically call the object operations 
         // ...
         }
};

其余的取决于您的全局设计。

例如,您可以设计一个工厂,该工厂将根据处理的事件创建命令并执行这些命令:

if (event==...) {
    // todo: find object pointed to by the object
    Command c = myfactory_for_event (object, parameters); 
    c.execute(); 
}  

如果您的 xml 文件用于配置对象,则读取该文件,创建特定命令,并将它们存储在将事件名称与具体命令相关联的事件映射中:

map<string,Command*> commands;   

在这种情况下,事件处理将如下所示:

myobject["initialize"]->execute();  

所以没有人知道是否有办法做到这一点?显然有黑客方法。 多个集成,多个工厂等。 我只能将事件的响应作为命令对象完成,但这似乎是一个奇怪的解决方案......因为没有可用的运行时信息,我必须将每个 INDIVIDUAL 函数封装在自己的类中。如果你问我,浪费内存...才意识到这听起来有多奇怪。基本上,我的意思是对于我想映射到事件的每个功能,我必须创建一个全新的类(与我已经用于映射键的命令模式相同)。 如果我能提供一个函数地址,而不是为单个操作分配和解除分配内存,那会容易得多,但似乎没有人有答案。