Scala Map由Type索引

时间:2015-07-16 09:22:28

标签: scala

我尝试制作Set - 就像只接受trait的每个子类型的一个条目一样。我的代码目前是:

import scala.reflect.ClassTag

trait Component
case class Entity(id: Int)

case class ComponentA(some: String) extends Component
case class ComponentB(other: Int) extends Component
case class ComponentC(thing: Boolean) extends Component

val components: Map[Entity, Set[Component]] = Map(Entity(1) -> Set(ComponentA("A"), ComponentB(1)))
def getComponent[A <: Component: ClassTag](entity: Entity): Option[A] = {
  components.getOrElse(entity, Nil).collectFirst { case c: A => c }
}

getComponent[ComponentA](Entity(1))
getComponent[ComponentB](Entity(1))
getComponent[ComponentC](Entity(1))

我的实体列表将是数百万,但每个实体的组件将是~100。是否有更快的方法,使用某种类型的索引映射,以防止每次读取时出现 O(n) collectFirst,并且每个读取都有filter + append更新

我尝试了很多东西,但是我能得到的最接近的是创建一个用于ComponentType密钥的Map[ComponentType, Component]特征,然后让getComponent方法返回一个通用Component而不是实际请求的类型。这使我在运行时或每个调用者的模式匹配中键入cast,而不是让编译器为我投射它。

任何其他类型,枚举的组合,可能是专门的SetMap类型,可以在这里提供帮助吗?

也许所有类型检查都不值得平均只进行100/2次迭代的开销,但我没有什么比较性能的。

1 个答案:

答案 0 :(得分:3)

您可以使用ComponentType作为二级结构来避免创建Map[ClassTag[_], Component]特征:

val components: Map[Entity, Map[ClassTag[_], Component]] = Map(
  Entity(1) -> Map(
    implicitly[ClassTag[ComponentA]] -> ComponentA("A"), 
    implicitly[ClassTag[ComponentB]] -> ComponentB(1))
)

def getComponent[A <: Component](entity: Entity)(implicit tag: ClassTag[A]): Option[A] = {
  components.getOrElse(entity, Map[ClassTag[_], Component]()).get(tag).asInstanceOf[Option[A]]
}

与实施addComponent的方式相同:

def addComponent[A <: Component](entity: Entity, component: A)(implicit tag: ClassTag[A]): Map[Entity, Map[ClassTag[_], Component]] = {
  components + (entity -> (components.getOrElse(entity, Map[ClassTag[_], Component]()) + (tag -> component)))
}

你无法避免以这种方式进行投射,但如果你在components课程中隐藏Components地图的实现,那么我认为你可以非常安全:

class Components {
  private val components: Map[Entity, Map[ClassTag[_], Component]] = Map()
  def getComponent[A <: Component](entity: Entity)(implicit tag: ClassTag[A]): Option[A] = ???
  def addComponent[A <: Component](entity: Entity, component: A)(implicit tag: ClassTag[A]): Components = ???
}

<强>更新: 基于@RüdigerKlaehn的评论,我修改了代码,它现在更具可读性:

val components: Map[(Entity, ClassTag[_]), Component] = Map(
  (Entity(1), implicitly[ClassTag[ComponentA]]) -> ComponentA("A"),
  (Entity(1), implicitly[ClassTag[ComponentB]]) -> ComponentB(1)
)

def getComponent[A <: Component](entity: Entity)(implicit tag: ClassTag[A]): Option[A] = {
  components.get((entity, tag)).asInstanceOf[Option[A]]
}

def addComponent[A <: Component](entity: Entity, component: A)(implicit tag: ClassTag[A]): Map[(Entity, ClassTag[_]), Component] = {
  components + ((entity, tag) -> component)
}