【问题标题】:Nim - Create sequence of objects which implement a methodNim - 创建实现方法的对象序列
【发布时间】:2016-03-20 16:50:01
【问题描述】:

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

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

我希望某些实体可更新但不可渲染,而某些实体应两者兼而有之。


哈斯克尔:

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

现在我想知道如何在 nim 中使用多种方法来实现。

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

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



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

【问题讨论】:

  • 似乎 concepts 是要走的路,但它们仍然是 WIP,我无法创建 seq[Updatable]type Updateable = concept x update(x)
  • 尚不清楚您希望如何在示例中使用多重继承。例如,如果组件没有“实现”render() 或 update(),为什么不干脆什么都不做呢?查看流行引擎的基于组件的架构(Unity3d、UE4),它们不使用用户实现的核心类的接口 - 考虑单个“组件”类型比考虑可能需要实现的许多接口更容易。跨度>

标签: interface typeclass nim-lang multimethod


【解决方案1】:

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

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

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

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

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

请有更多知识的人告诉我们!

【讨论】:

  • 感谢您的回答。基于公共超类的动态调度仍然可以完成,但我尝试通过接口(不存在)模拟多重继承。我将使用不同类型的多个列表。
  • 再想一想,我想也可以有行为列表,然后根据对这些列表的引用来组合游戏对象。
【解决方案2】:

缺少明确的interface 关键字是common question in the Nim community。将 Araq 的答案应用到基于您的 Java/D sn-p 的假设案例中,我们可以编写如下内容:

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,具体取决于您需要什么接口,其他方法可能会更好。另外,大概未来的路是concepts,但像往常一样,手册很枯燥,the related unit tests are cryptic 所以我无法将前面的元组示例转换为概念。

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

【讨论】:

  • 概念方法似乎是一条死胡同。概念没有运行时表示。永远无法创建seq[Updateable]。使用闭包元组感觉不对。这就像构建自己的 vtable,因为编译器不能。我希望 Nim 在 1.0 版本之前得到接口
  • 总的来说,Nim 的想法是leaves you the possibility to implement such features,就像没有官方的 OOP 宏一样,but you can build your own
【解决方案3】:

Swift 也有同样的问题,他们在那里使用类型擦除,这与之前的 cmets 中提出的相同,但更加结构化。 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,但更重要的是可以转换为AnyCAny* 类型通常包含闭包以有效擦除类型,并通过简单地转发参数来实现 concept 本身。

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

【讨论】:

    猜你喜欢
    • 2020-05-25
    • 2014-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    相关资源
    最近更新 更多