我正在夏季期间将游戏引擎作为一个项目。每个可编写脚本的组件都应该可以访问它们所在场景中的某些方法。为了实现这一点,我将lambdas从调用相应方法的场景传递给脚本表,在脚本表中它们被隐式转换为std :: function类型。
Scene.h:
function draw() {
//draw in the container
c.fillStyle = "#000000";
c.fillRect(container.y, container.x, container.width, container.height);
//draw first node
c.arc(node1.x, node1.y, node1.r, 0, 2*Math.PI);
c.fillStyle = node1.color;
c.fill();
//draw second node
c.arc(node2.x, node2.y, node2.r, 0, 2*Math.PI);
c.strokeStyle = node2.color;
c.fillStyle = node2.color;
c.fill();
//draw muscle
c.beginPath();
c.moveTo(muscle.node1x, muscle.node1y);
c.lineTo(muscle.node2x, muscle.node2y);
c.strokeStyle = muscle.color;
c.lineWidth = muscle.width;
c.stroke();
}
回调方法适用于我需要访问的非模板函数,但不适用于模板函数,所以它不可能。
脚本化:
class Scene
{
private:
unsigned int _currentId;
std::vector<System*> _systems;
//SCRIPTABLE NEEDS THE BELOW METHODS THESE EXCLUSIVELY:
bool exists(unsigned id);
void destroy(unsigned int);
void addComponent(Component*, unsigned int);
template<typename T> T& getComponent(unsigned int);
template<typename T> bool hasComponent(unsigned int);
template<typename T> void removeComponent(unsigned int);
protected:
unsigned int instantiate(std::vector<Component*>);
public:
Scene(ChangeSceneCallback);
~Scene();
void initiate();
void update(long dt);
};
template<typename T>
inline T & Scene::getComponent(unsigned int id)
{
for (System* system : _systems) {
if (system->corresponds(T)) {
return static_cast<T*>(system->getComponent(entityId));
}
}
}
template<typename T>
inline bool Scene::hasComponent(unsigned int id)
{
for (System* system : _systems) {
if (system->corresponds(T)) {
return system->contains(id);
}
}
}
template<typename T>
inline void Scene::removeComponent(unsigned int id)
{
for (System* system : _systems) {
if (system->corresponds(T)) {
return system->destroy(id);
}
}
}
Scriptable无法访问场景中的公共方法,因为这会让用户/开发人员访问它们(Scriptable是游戏行为的基类)。这就是为什么我需要提出一些可以编写脚本的有限访问场景的东西。
有什么想法吗?
答案 0 :(得分:1)
您无法删除类型&#34;模板回调&#34;。您必须在模板或类型擦除之间进行选择。让我解释一下。
这就是&#34;模板回调&#34;看起来像。这实际上是一个普通的lambda:
auto print_callback = [](auto var) {
std::cout << var << std::endl;
}
print_callback(4) ; // prints "4"
print_callback(4.5); // prints "4.5"
print_callback("hello"); // prints "hello"
看起来不错,但请注意,您无法使用std::function
执行此操作,因为您必须预定义签名。
std::function<void(int)> func_print_callback = print_callback;
func_print_callback(5); // Yay! Prints "5"
func_print_callback("hello"); // error
问题是,您可能认为限制仅仅是因为std::function
需要特定的签名才能使用,但限制要比这更深。
问题是,没有模板功能。它们不存在。另一方面,功能模板确实存在。为什么我如此强调我的单词的顺序是因为这个东西的名字说明了一切:它不是函数,它是用于制作的模板功能
这是一个简单的例子:
template<typename T>
void foo(T t) {
std::cout << t << std::endl;
}
此函数未编译。因为它不是一个功能。在孔foo
填满之前,不存在任何函数T
。
如何填充名为
T
的洞应该是一个类型?
填写一种课程!
foo(5.4); // the hole T is `double`
当编译器看到这个时,它知道你需要一个名为foo
的函数,它接受一个double作为参数。没有名为foo
的函数需要double
。但是我们给编译器一个工具来创建一个:模板!
因此编译器将生成此函数:
void foo_double(double t) {
std::cout << t std::endl;
}
这里的字是:生成。编译器需要创建函数才能存在。编译器为您生成代码。
生成并编译函数时,T
不再存在。模板参数是编译时实体,只有编译器知道它们。
现在,我将向您解释为什么没有模板回调这样的事情。
使用指向函数的指针实现类型擦除容器,例如std::function
。我将使用类型别名来简化语法。它的工作原理如下:
// A function
void foo(int) {}
// The type of the pointer to function
using func_ptr = void(*)(int);
// A pointer to foo
func_ptr ptr = &foo;
指向函数foo的指针有一个值,指向内存中foo
的位置。
现在假设我们有办法拥有模板函数指针。我们必须指向一个尚不存在的函数。它没有内存位置,所以它没有意义。通过指针,当作为函数调用时,您必须生成函数代码。
由于指向函数的指针可以指向任何函数,即使编译器尚未知道的函数,您也必须以某种方式生成函数代码并对其进行编译。但是指针的值,我们的指针所指向的函数,是在运行时定义的!因此,当编译器不再存在时,您必须在运行时编译代码,因为您还不知道的代码是不存在的值。如您所见,指向模板函数,模板std::function
或虚拟模板函数的指针不存在。
现在你已经理解了这个问题,让我提出一个解决方案:删除回调用法。你应该直接调用这些函数。
您似乎只使用回调来调用私有成员函数。这是错误的方法,即使它有效。您需要的是friend
,这是C ++的一项功能,允许您访问私有成员。
class Scene {
friend Component;
// ...
};
class Component {
protected:
// Let `scene` be a reference to your scene
void addComponent(Component* c, unsigned int id) {
scene.addComponent(c, id);
}
template<typename T>
T& getComponent(unsigned int id) {
return scene.getComponent<T>(id);
}
template<typename T>
bool hasComponent(unsigned int id) {
return scene.hasComponent(id);
}
template<typename T>
void removeComponent(unsigned int id) {
removeComponent(id);
}
// ...
};
由于Component
类是Scene
的唯一朋友,因此只能调用私有成员函数。由于Component
中所有新定义的函数都受到保护,因此只有从Component
扩展的类才能调用这些函数。它们被调用如下:
class Scriptable : public Component {
void foo() {
hasComponent<Bar>(87); // works, call function defined in `Component`
}
};