Interface Nim-创建实现方法的对象序列
我想编程一个游戏,并希望使用多个实体的组件模式 在具有接口/类型类/多重继承的语言中,不会有问题 我希望有些实体是可更新的,但不可渲染的,有些实体应该两者兼而有之Interface Nim-创建实现方法的对象序列,interface,typeclass,nim-lang,multimethod,Interface,Typeclass,Nim Lang,Multimethod,我想编程一个游戏,并希望使用多个实体的组件模式 在具有接口/类型类/多重继承的语言中,不会有问题 我希望有些实体是可更新的,但不可渲染的,有些实体应该两者兼而有之 哈斯克尔: class Updateable a where update :: Float -> a -> a class Renderable a where render :: a -> Picture class InputHandler a where handleInput
哈斯克尔:
class Updateable a where
update :: Float -> a -> a
class Renderable a where
render :: a -> Picture
class InputHandler a where
handleInput :: Event -> a -> a
我可以创建一个可以更新的列表
updateAll :: Updateable a => Float -> [a] -> [a]
updateAll delta objs = map (update delta) objs
在Java/D/中。。。这可以通过接口实现
interface Updateable {
void update(float delta);
}
// somewhere in a method
List<Updateable> objs = ...;
for (Updateable o : objs) {
o.update(delta);
}
编辑:添加了更多代码并修复了不正确的Haskell示例缺少明确的
界面
关键字是错误的。将Araq的答案应用到基于Java/D片段的假设案例中,我们可以编写如下内容:
import strutils # For formatFloat
type
IUpdateable =
tuple[
update: proc(v: float) {.closure.},
show: proc(): string {.closure.}
]
Rounded = ref object
internalValue: float
Real = ref object
a_real_value: float
# Here goes our rounded type.
proc `$`(x: Rounded): string =
result = "Rounded{" & $int(x.internalValue) & "}"
proc updateRounded(x: Rounded, delta: float) =
x.internalValue += delta
proc getUpdateable(x: Rounded): IUpdateable =
result = (
update: proc(v: float) = x.updateRounded(v),
show: proc(): string = `$`(x)
)
converter toIUpdateable(x: Rounded): IUpdateable =
result = x.getUpdateable
# Here goes our Real type.
proc `$`(x: Real): string =
result = "Real{" &
x.a_real_value.format_float(precision = 3) & "}"
proc update_real(x: Real, delta: float) =
x.a_real_value += delta
proc getUpdateable(x: Real): IUpdateable =
result = (
update: proc(v: float) = x.update_real(v),
show: proc(): string = `$`(x)
)
# Here goes the usage
proc main() =
var objs: seq[IUpdateable] = @[]
var a = Rounded()
var b = Real()
a.internalValue = 3.5
b.a_real_value = 3.5
objs.add(a) # works because of toIUpdateable()
objs.add(b.getUpdateable)
for obj in objs:
echo "Going through one loop iteration"
echo "\t", obj.show()
obj.update(0.4)
echo "\t", obj.show()
obj.update(0.4)
echo "\t", obj.show()
main()
# -> Going through one loop iteration
# -> Rounded{3}
# -> Rounded{3}
# -> Rounded{4}
# -> Going through one loop iteration
# -> Real{3.50}
# -> Real{3.90}
# -> Real{4.30}
#-------------------------------------------------------------
# types
#-------------------------------------------------------------
type C = concept type C
proc name(x: C, msg: string): string
type AnyC = object
name: proc(msg: string): string # doesn't contain C
type A = object
type B = object
#-------------------------------------------------------------
# procs
#-------------------------------------------------------------
proc name(x: A, msg: string): string = "A" & msg
proc name(x: B, msg: string): string = "B" & msg
proc name(x: AnyC, msg: string): string = x.name(msg) # AnyC implements C
proc to_any(x: A): AnyC = AnyC(
name: proc (msg: string): string = name(x, msg) # x captured by proc
)
proc to_any(x: B): AnyC = AnyC(
name: proc (msg: string): string = name(x, msg) # x captured by proc
)
# actually use C
proc print_name(x: C, msg: string) = echo x.name(msg)
#-------------------------------------------------------------
# main
#-------------------------------------------------------------
let a = A()
let b = B()
let cs = [a.to_any(), b.to_any()] # the main goal of most erasure cases
for c in cs:
c.print_name(" erased") # e.g. "A erased"
然而,正如您所能做的,根据您对其他方法的接口的确切需求,可能会更好。另外,可能未来的发展方向是,但与往常一样,手册是枯燥的,因此我无法将前面的元组示例转换为概念
如果你想学习概念,你应该直接在论坛上提问,但要注意,正如手册上所说,。我不确定这是否回答了你的问题,但值得一提 如果要根据类型将游戏对象存储在单独的列表中,您仍然可以编写许多通用逻辑。由于预读和分支预测,按类型存储对象具有更好的性能。看这个讲座,从一个应该知道他在说什么的人那里: 例如,如果已为某些对象类型定义了
纹理
过程,则可以编写一个通用的绘制(t:t)=magicRenderToScreen(纹理(t))
过程,该过程将适用于所有对象类型。如果您正在实现资源池或任何类型的常规行为,这也很有用
您确实必须以某种方式在渲染和更新循环中包含每个受影响的对象类型,但在实践中这通常不是什么大问题。您甚至可以使用一个简单的宏来减少这种冗长,因此您的渲染循环只包含类似于renderAll(玩家、敌人、精灵、平铺)
通用列表在编译语言中并不简单,nim强迫您查看它,这在您编写游戏时是一种好方法。要拥有泛型列表,通常必须使用指针和动态分派,或者某种类型的联合类型。我似乎记得nim曾经能够从父对象ref分派到正确的多方法(这将使列表能够包含多种类型并在运行时动态分派),但我真的不确定这是否仍然可以做到
有更多的知识请让我们知道 Swift也有同样的问题,他们在那里使用类型擦除,这与前面的评论中提出的相同,但有点僵化。Nim中的一般模式如下所示:
import strutils # For formatFloat
type
IUpdateable =
tuple[
update: proc(v: float) {.closure.},
show: proc(): string {.closure.}
]
Rounded = ref object
internalValue: float
Real = ref object
a_real_value: float
# Here goes our rounded type.
proc `$`(x: Rounded): string =
result = "Rounded{" & $int(x.internalValue) & "}"
proc updateRounded(x: Rounded, delta: float) =
x.internalValue += delta
proc getUpdateable(x: Rounded): IUpdateable =
result = (
update: proc(v: float) = x.updateRounded(v),
show: proc(): string = `$`(x)
)
converter toIUpdateable(x: Rounded): IUpdateable =
result = x.getUpdateable
# Here goes our Real type.
proc `$`(x: Real): string =
result = "Real{" &
x.a_real_value.format_float(precision = 3) & "}"
proc update_real(x: Real, delta: float) =
x.a_real_value += delta
proc getUpdateable(x: Real): IUpdateable =
result = (
update: proc(v: float) = x.update_real(v),
show: proc(): string = `$`(x)
)
# Here goes the usage
proc main() =
var objs: seq[IUpdateable] = @[]
var a = Rounded()
var b = Real()
a.internalValue = 3.5
b.a_real_value = 3.5
objs.add(a) # works because of toIUpdateable()
objs.add(b.getUpdateable)
for obj in objs:
echo "Going through one loop iteration"
echo "\t", obj.show()
obj.update(0.4)
echo "\t", obj.show()
obj.update(0.4)
echo "\t", obj.show()
main()
# -> Going through one loop iteration
# -> Rounded{3}
# -> Rounded{3}
# -> Rounded{4}
# -> Going through one loop iteration
# -> Real{3.50}
# -> Real{3.90}
# -> Real{4.30}
#-------------------------------------------------------------
# types
#-------------------------------------------------------------
type C = concept type C
proc name(x: C, msg: string): string
type AnyC = object
name: proc(msg: string): string # doesn't contain C
type A = object
type B = object
#-------------------------------------------------------------
# procs
#-------------------------------------------------------------
proc name(x: A, msg: string): string = "A" & msg
proc name(x: B, msg: string): string = "B" & msg
proc name(x: AnyC, msg: string): string = x.name(msg) # AnyC implements C
proc to_any(x: A): AnyC = AnyC(
name: proc (msg: string): string = name(x, msg) # x captured by proc
)
proc to_any(x: B): AnyC = AnyC(
name: proc (msg: string): string = name(x, msg) # x captured by proc
)
# actually use C
proc print_name(x: C, msg: string) = echo x.name(msg)
#-------------------------------------------------------------
# main
#-------------------------------------------------------------
let a = A()
let b = B()
let cs = [a.to_any(), b.to_any()] # the main goal of most erasure cases
for c in cs:
c.print_name(" erased") # e.g. "A erased"
在此示例中,AnyC
实现了C
,A
和B
也实现了C
,但更重要的是可以转换为AnyC
。Any*
类型通常包含闭包,以有效地删除该类型,并通过简单地转发参数来实现概念本身
我希望有一个宏或什么东西可以实现Any*
和自动执行任何。似乎是一条路要走,但它们仍然是WIP,我无法创建seq[updateable]
type Updateable=concept x update(x)
不清楚您希望在示例中如何使用多重继承。例如,如果组件没有“实现”render()或update(),为什么不干脆什么都不做呢?看看流行引擎的基于组件的架构(Unity3d、UE4),它们不使用用户实现的核心类的接口——考虑单个“组件”类型比考虑需要实现的许多接口更容易。概念方法似乎是一条死胡同。概念没有运行时表示。人们永远无法创建seq[Updateable]
。使用闭包的元组感觉不太好。这就像构建自己的vtable,因为编译器不能。我希望Nim在1.0发行版之前获得接口一般来说,Nim的想法是,就像没有官方的OOP宏一样,谢谢你的回答。基于公共超类的动态调度仍然可以完成,但我尝试通过接口(不存在)模拟多重继承。我将使用不同类型的多个列表。仔细想想,我想也可以有行为列表,然后根据这些列表的引用组合游戏对象。