Nim - Создать последовательность объектов, которые реализуют метод

Я хочу запрограммировать игру и использовать шаблон компонента для нескольких объектов.

В языке с интерфейсами / типами-классами / множественным наследованием проблем не было бы.

Я хочу, чтобы некоторые объекты были обновляемыми, но не воспроизводимыми, а некоторые должны быть обоими.


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

Теперь мне интересно, как это можно реализовать в NIM с помощью мультиметодов.

Может ли существование подходящего мультиметода быть выражено в виде?

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



Редактировать: добавлено больше кода и исправлен неверный пример на Haskell

2 ответа

Решение

Я не уверен, что это ответит на ваш вопрос, но стоит упомянуть.

Если бы вы хранили свои игровые объекты в отдельных списках в зависимости от типа, вы все равно могли бы написать много общей логики. Хранение объектов по типу имеет лучшую производительность из-за упреждающего чтения и прогнозирования переходов. Посмотрите эту лекцию от парня, который должен знать, о чем он говорит: многопроцессорные игровые циклы: уроки из неизведанного 2: среди воров.

Например, если вы определили texture proc для некоторых типов ваших объектов, тогда вы можете написать общий draw(t: T) = magicRenderToScreen(texture(t)) Proc, который будет работать для всех них. Это также полезно, если вы реализуете пулы ресурсов или какой-либо другой тип общего поведения.

Вы должны как-то включать каждый затронутый тип объекта в циклы рендеринга и обновления, но на практике это обычно не имеет большого значения. Вы можете даже использовать простой макрос, чтобы сделать его менее подробным, поэтому ваш цикл рендеринга просто содержит что-то вроде renderAll(players, enemies, sprites, tiles)

Общие списки не являются прямыми в скомпилированных языках, и nim заставляет вас их видеть, что неплохо, когда вы работаете над игрой. Чтобы иметь общие списки, вы обычно должны использовать указатели и динамическую диспетчеризацию, или какой-то тип объединения. Кажется, я помню, что nim раньше имел возможность отправлять правильные мульти-методы из ссылок родительского объекта (что позволило бы спискам содержать несколько типов и динамически отправлять их во время выполнения), но я, честно говоря, не уверен, что это все еще может быть сделанный...?

Кто-то более знающий, пожалуйста, дайте нам знать!

Отсутствие явного interface ключевое слово - общий вопрос в сообществе Nim. Взяв ответ Арака и применив его к гипотетическому случаю, основанному на вашем фрагменте 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}

Однако, как вы можете прочитать в этой ветке форума, в зависимости от того, какие именно интерфейсы вам нужны для других подходов, может быть лучше. Кроме того, предположительно, в будущем мы выберем концепты, но, как обычно, руководство сухое, а соответствующие модульные тесты загадочны, поэтому мне не удалось перевести предыдущий пример кортежа в концепции.

Если вы хотите найти концепцию, вы должны спросить об этом непосредственно на форуме, но будьте осторожны, как говорится в руководстве, концепции все еще находятся в разработке.

У 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 орудия C, A а также B также реализовать C но, что более важно, можно преобразовать в AnyC. ВAny* типы обычно содержат замыкания для эффективного стирания типа, а также реализуют concept сам путем тривиальной пересылки аргументов.

Хотелось бы, чтобы был макрос или что-то, что реализовало бы Any* а также to_any автоматически.

Другие вопросы по тегам