Nim - 创建实现方法的对象序列

Nim - Create sequence of objects which implement a method

我想编写一个游戏,并希望为多个实体使用组件模式。

在具有接口/类型-类/多重继承的语言中不会有问题。

我希望某些实体可更新但不可渲染,而有些则两者都可以。


Haskell:

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);
}

现在我想知道如何使用 multimethods 在 nim 中实现它。

拟合多方法的存在可以用类型表示吗?

var objs: seq[???] = @[]



编辑:添加了更多代码并修复了不正确的 Haskell 示例

缺少明确的 interface 关键字是 common question in the Nim community。根据你的 Java/D 片段,将 Araq 的答案应用到一个假设案例中,我们可以这样写:

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}

但是,您可以 read in that forum thread, depending on what exactly you need interfaces for other approaches may be better. Also, presumably the future way to go are concepts, but as usual the manual is dry and the related unit tests are cryptic 所以我无法将前面的元组示例转换为概念。

如果您想了解概念,您应该直接在论坛中提问,但请注意,正如手册所述,concepts are still in development

我不确定这是否回答了您的问题,但值得一提。

如果您要根据类型将游戏对象存储在单独的列表中,您仍然可以编写大量通用逻辑。由于预读和分支预测,按类型存储对象具有更好的性能。看到这个讲座,来自一个应该知道他在说什么的人:Multiprocessor Game Loops: Lessons from Uncharted 2: Among Thieves

例如,如果您为某些对象类型定义了一个 texture 过程,那么您可以编写一个适用于所有对象类型的通用 draw(t: T) = magicRenderToScreen(texture(t)) 过程。如果您正在实施资源池或任何类型的一般行为,这也很有用。

您确实必须以某种方式在渲染和更新循环中包含每个受影响的对象类型,但这在实践中通常不是什么大问题。您甚至可以使用一个简单的宏来减少冗长,因此您的渲染循环只包含类似 renderAll(players, enemies, sprites, tiles)

的内容

通用列表在编译语言中并不简单,nim 会强制您看到它,这在您开发游戏时很有用。要拥有通用列表,您通常必须使用指针和动态调度,或者某种联合类型。我似乎记得 nim 曾经能够从父对象引用分派到正确的多种方法,(这将使列表包含多种类型并在运行时动态分派)但老实说我不确定是否仍然可以完成...?

有更懂行的请告诉我们!

Swift 有同样的问题,他们使用类型擦除,这与之前的评论中提出的相同,但结构更合理。 Nim 中的一般模式是这样的:

#-------------------------------------------------------------
# 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 实现了 CAB 也实现了 C,但更重要的是可以转换为 AnyC . Any* 类型通常包含闭包以有效地擦除类型并通过简单的转发参数实现 concept 本身。

我希望有一个宏或可以自动实现 Any*to_any 的东西。