Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoiding reflection in EntityActions tutorial #7

Open
nanodeath opened this issue Mar 7, 2021 · 1 comment
Open

Avoiding reflection in EntityActions tutorial #7

nanodeath opened this issue Mar 7, 2021 · 1 comment

Comments

@nanodeath
Copy link

nanodeath commented Mar 7, 2021

In the tutorial, there's a bit of code that looks like this:

    fun createActionsFor(                                               // 2
            context: GameContext,
            source: GameEntity<EntityType>,
            target: GameEntity<EntityType>
    ): Iterable<EntityAction<out EntityType, out EntityType>> {
        return actions.map {
            try {
                it.constructors.first().call(context, source, target)   // 3
            } catch (e: Exception) {                                    // 4
                throw IllegalArgumentException("Can't create EntityAction. Does it have the proper constructor?")
            }
        }
    }

which isn't the best...ideally we'd be able to avoid calling things using reflection. Well, we can, we just need to pass factories that build EntityActions instead of EntityAction classes.

This also happens to be, IMO, a great opportunity to showcase one of the few big selling features of companion objects: they can implement interfaces, and so you can use the companion object directly as the factory. Here's what I'm doing and I think it's pretty neat: (btw the diff is kinda backwards, so I'd recommend reading from the bottom)

diff --git a/src/main/kotlin/com/example/cavesofzircon/attributes/EntityActions.kt b/src/main/kotlin/com/example/cavesofzircon/attributes/EntityActions.kt
index 3a3d58d..c9c61d7 100644
--- a/src/main/kotlin/com/example/cavesofzircon/attributes/EntityActions.kt
+++ b/src/main/kotlin/com/example/cavesofzircon/attributes/EntityActions.kt
@@ -2,6 +2,7 @@ package com.example.cavesofzircon.attributes

 import com.example.cavesofzircon.extensions.AnyGameEntity
 import com.example.cavesofzircon.messages.EntityAction
+import com.example.cavesofzircon.messages.EntityActionBuilder
 import com.example.cavesofzircon.world.GameContext
 import org.hexworks.amethyst.api.base.BaseAttribute
 import org.hexworks.amethyst.api.entity.EntityType
@@ -9,7 +10,7 @@ import kotlin.reflect.KClass

 // TODO replace with companion objects w/ interfaces
 class EntityActions(
-    private vararg val actions: KClass<out EntityAction<out EntityType, out EntityType>>
+    private vararg val actions: EntityActionBuilder<out EntityType, out EntityType>
 ) : BaseAttribute() {
     fun createActionsFor(
         context: GameContext,
@@ -17,11 +18,7 @@ class EntityActions(
         target: AnyGameEntity
     ): Iterable<EntityAction<out EntityType, out EntityType>> {
         return actions.map { action ->
-            try {
-                action.constructors.first().call(context, source, target)
-            } catch (e: Exception) {
-                throw IllegalArgumentException("Can't create EntityAction. Does it have the proper constructor? $action")
-            }
+            action.create(context, source, target)
         }
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/example/cavesofzircon/builders/EntityFactory.kt b/src/main/kotlin/com/example/cavesofzircon/builders/EntityFactory.kt
index e008bb9..3b4d69a 100644
--- a/src/main/kotlin/com/example/cavesofzircon/builders/EntityFactory.kt
+++ b/src/main/kotlin/com/example/cavesofzircon/builders/EntityFactory.kt
@@ -28,7 +28,7 @@ object EntityFactory {
         attributes(
             EntityPosition(),
             EntityTile(PLAYER),
-            EntityActions(Dig::class)
+            EntityActions(Dig)
         )
         behaviors(InputReceiver)
         facets(Movable, CameraMover)
diff --git a/src/main/kotlin/com/example/cavesofzircon/messages/Dig.kt b/src/main/kotlin/com/example/cavesofzircon/messages/Dig.kt
index 22e3caf..b1afdc8 100644
--- a/src/main/kotlin/com/example/cavesofzircon/messages/Dig.kt
+++ b/src/main/kotlin/com/example/cavesofzircon/messages/Dig.kt
@@ -1,11 +1,20 @@
 package com.example.cavesofzircon.messages

+import com.example.cavesofzircon.extensions.AnyGameEntity
 import com.example.cavesofzircon.extensions.GameEntity
 import com.example.cavesofzircon.world.GameContext
 import org.hexworks.amethyst.api.entity.EntityType

 data class Dig(
     override val context: GameContext,
-    override val source: GameEntity<EntityType>,
-    override val target: GameEntity<EntityType>
-) : EntityAction<EntityType, EntityType>
\ No newline at end of file
+    override val source: AnyGameEntity,
+    override val target: AnyGameEntity
+) : EntityAction<EntityType, EntityType> {
+    companion object : EntityActionBuilder<EntityType, EntityType> {
+        override fun create(
+            context: GameContext,
+            source: AnyGameEntity,
+            target: AnyGameEntity
+        ) = Dig(context, source, target)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/example/cavesofzircon/messages/EntityAction.kt b/src/main/kotlin/com/example/cavesofzircon/messages/EntityAction.kt
index 2f8e132..96b8206 100644
--- a/src/main/kotlin/com/example/cavesofzircon/messages/EntityAction.kt
+++ b/src/main/kotlin/com/example/cavesofzircon/messages/EntityAction.kt
@@ -2,6 +2,7 @@ package com.example.cavesofzircon.messages

 import com.example.cavesofzircon.extensions.GameEntity
 import com.example.cavesofzircon.extensions.GameMessage
+import com.example.cavesofzircon.world.GameContext
 import org.hexworks.amethyst.api.entity.EntityType

 interface EntityAction<S: EntityType, T: EntityType> : GameMessage {
@@ -11,3 +12,11 @@ interface EntityAction<S: EntityType, T: EntityType> : GameMessage {
     operator fun component2() = source
     operator fun component3() = target
 }
+
+interface EntityActionBuilder<S: EntityType, T: EntityType> {
+    fun create(
+        context: GameContext,
+        source: GameEntity<S>,
+        target: GameEntity<T>
+    ) : EntityAction<S, T>
+}

Only thing I don't love is this

data class Dig(
    override val context: GameContext,
    override val source: AnyGameEntity,
    override val target: AnyGameEntity
) : EntityAction<EntityType, EntityType> {
    companion object : EntityActionBuilder<EntityType, EntityType> {
        override fun create(
            context: GameContext,
            source: AnyGameEntity,
            target: AnyGameEntity
        ) = Dig(context, source, target)
    }
}

feels annoyingly verbose. But better than reflection maybe.

@nanodeath
Copy link
Author

I ran into some mess with generics in tutorial 8 as a result of this and had to add the following code:

class EntityActions(
    private vararg val actions: EntityActionBuilder<*, *>
) : BaseAttribute() {
    fun createActionsFor(
        context: GameContext,
        source: AnyGameEntity,
        target: AnyGameEntity
    ): Iterable<EntityAction<*, *>> {
        return actions.map { action ->
            // TODO see if I can fix these casts
            action.create(context, source as Entity<Nothing, GameContext>, target as Entity<Nothing, GameContext>)
        }
    }
}

getting the types right so source and target are happy is a challenge. So I don't 100% endorse my original solution, but the ugly cast might still be better overall.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant