Karroffel
Karroffel

Reputation: 115

Nim - Create sequence of objects which implement a method

I want to program a game and would like to use a component pattern for multiple entities.

In a language with interfaces / type-classes / multiple inheritance there would be no problem.

I want some entities to be updateable but not renderable and some shall be both.


Haskell:

class Updateable a where
    update :: Float -> a -> a

class Renderable a where
    render :: a -> Picture

class InputHandler a where
    handleInput :: Event -> a -> a

I can create a list of things that can be updated.

updateAll :: Updateable a => Float -> [a] -> [a]
updateAll delta objs = map (update delta) objs

In Java/D/... this could be implemented via Interfaces

interface Updateable {
    void update(float delta);
}

// somewhere in a method
List<Updateable> objs = ...;
for (Updateable o : objs) {
    o.update(delta);
}

Now I am wondering how this can be implemented in nim with multimethods.

Can the existence of a fitting multimethod be expressed in a type?

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



Edit: Added more code and fixed incorrect Haskell example

Upvotes: 4

Views: 2638

Answers (3)

Cristian Garcia
Cristian Garcia

Reputation: 9859

Swift has the same problem and there they use Type Erasure, which is the same as proposed in the previous comments but a bit more strutured. The general pattern in Nim is like this:

#-------------------------------------------------------------
# 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"

In this example AnyC implements C, A and B also implement C but more importantly can be converted to AnyC. The Any* types usually contain closures to effectively erase the type and also implement the concept itself by trivial forwarding the arguments.

I wish there was a macro or something that would implement Any* and to_any automatically.

Upvotes: 0

Jostein
Jostein

Reputation: 3144

I'm not sure if this answers your question, but it's worth mentioning.

If you were to store you game objects in separate lists based on type, you could still write a lot of generic logic. Storing objects by type has better better performance because of read-ahead and branch prediction. See this lecture, from a guy who should know what he's talking about: Multiprocessor Game Loops: Lessons from Uncharted 2: Among Thieves.

For instance, if you have defined a texture proc for some of your object types, then you can write a generic draw(t: T) = magicRenderToScreen(texture(t)) proc that will work for all of them. This is also useful if you are implementing resource pools, or any kind of general behaviour really.

You do have to include each affected object type in the render and update loops somehow, but that's usually not a big deal in practice. You can even use a simple macro to make this less verbose, so your render loop simply contains something like renderAll(players, enemies, sprites, tiles)

Generic lists are not straightforward in compiled languages, and nim forces you to see it, which is kind of good when you're working on a game. To have generic lists you typically either have to use pointers and dynamic dispatch, or some kind of union type. I seem to remember that nim used to be able to dispatch to the correct multi-methods from parent object ref's, (which would enable lists to contain several types and dispatch dynamically at runtime) but I'm honestly not sure if that can still be done...?

Someone more knowledgeable please let us know!

Upvotes: 4

Grzegorz Adam Hankiewicz
Grzegorz Adam Hankiewicz

Reputation: 7681

The lack of an explicit interface keyword is common question in the Nim community. Taking Araq's answer and applying it to a hypothetical case based on your Java/D snippet we could write something like this:

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}

However, as you can 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 so I couldn't manage to translate the previous tuple example to concepts.

If you feel like going for concepts you should ask in the forum directly, but beware, as the manual says, concepts are still in development.

Upvotes: 3

Related Questions