AllayMC / Allay

The next-generation Minecraft: Bedrock Edition server software
https://docs.allaymc.org
GNU Lesser General Public License v3.0
159 stars 13 forks source link

疑问:关于基于继承组件设计的担忧 #21

Closed johnbanq closed 1 year ago

johnbanq commented 1 year ago

参照AttackComponent.java的内容,目前Allay内部有一套基于接口继承实现的组件系统。组件接口以getter/setter的形式指定状态,实体接口通过继承组件接口的方式来决定到底要拥有什么状态。具体实例将在运行时编织而成。

这么干确实非常精巧,但我担心这会限制系统的可扩展性,第三方插件是无法让现有接口继承新接口的。

目前基于组件的设计基本都在使用组合方式:即提供getComponent/addComponent这类成员,具体可以参考目前的ECS系统设计,例如artemis-odb https://github.com/junkdog/artemis-odb 和ashley https://github.com/libgdx/ashley

这么干的话可扩展性会很好,性能其实也可以通过运行时代码生成来完成——我们可以把组件实例变成实现类的特殊成员变量,然后在get的时候如果if匹配就直接返回,以便JIT内联。甚至可以将组件实例打开,直接存储其内容,以实现和继承不相上下的性能。

smartcmd commented 1 year ago

现在这个架构可以保证运行时强类型

smartcmd commented 1 year ago

如果完全动态化要怎么获得强类型支持呢?

johnbanq commented 1 year ago

现在这个架构可以保证运行时强类型

是的,这么搞的话类型安全会变弱,会无法知道某类实体在运行时拥有哪些组件。这是这个设计的一个弱点。

不过我认为组件化架构的重点是组件,实体只是组件的拼装而已,所以这里可以酌情牺牲。

另一种做法是在实体接口定义里定义组件的getter函数,用这种方法强制某类实体必须拥有某个组件。同时提供通用的getComponent方法方便扩展

smartcmd commented 1 year ago

我觉得应该评估是否真的需要这么强的动态性 如果需要在运行时动态增删组件,换成是我的话,我会选择新建一个实体定义 强类型带来的好处是很大的

johnbanq commented 1 year ago

我觉得应该评估是否真的需要这么强的动态性 如果需要在运行时动态增删组件,换成是我的话,我会选择新建一个实体定义 强类型带来的好处是很大的

那么可以考虑上面提到的做法,在实体接口定义里写组件的getter函数,明确定义某类实体有什么组件。同时保留通用方法,允许插件给实体挂载新组件。通用方法可以暂不提供,后续再做添加。这样既有类型安全,又有可扩展性,可扩展性还可以日后视情形加入。

例如这样:

interface Entity {

    // 通用方法,允许插件添加新组件, 可以暂不提供 //

   void addComponent(Component component);

    <T extends Component> void removeComponent(Class<T> clazz);

    <T extends Component> @Nullable T getComponent(Class<T> clazz); 

}

interface Sheep extends Entity {

    // 强制拥有这些组件 //

    PositionComponent getPositionComponent();

    HealthComponent getHealthComponent();

}

这是个涉及到偏好的问题,如果有了最终结论就留言关掉吧