Map with Class[T] as keys and ArrayBuffer[T] as values?

1,068 views
Skip to first unread message

sh...@yahoo.de

unread,
Nov 22, 2013, 7:00:22 AM11/22/13
to scala...@googlegroups.com
Hi everybody,

I'm new to scala. I want to declare and access a map like this:


// Obviously not working:
val componentMap = Map.empty[Class[T], ArrayBuffer[T]]  // <- I want the same type for key/value

def addComponent[T <: Component]( eId: Int, comp: T ) {
   
    val clzz = comp.getClass()
    if ( !componentMap.contains( clzz ) ) {
        componentMap += clzz -> new ArrayBuffer[T]
    }
   
    componentMap(clzz)(eId) = comp // Type Mismatch
}

def getComponent[T <: Component]( eId: Int, clzz: Class[T] ) : Option[T] = {
    if ( !componentMap.contains( clzz ) ) {
        return null
    }
   
    componentMap.get( clzz ).get( eId )   // Type Mismatch
}


So, in words: I want an ArrayBuffer for every subclass T (extending Component) stored in a map with the key Class[T].

I understand that the compiler is missing the necessary type information, but I don't know how to change that. I tried for some time now, but can't find a solution.

Thanks in advance for your suggestions!
Stefan

atomly

unread,
Nov 22, 2013, 6:19:33 PM11/22/13
to sh...@yahoo.de, scala-user
I think you could do this with type projection... something like a trait Container[T] with your type C = Class[T] and type AB = ArrayBuffer[T] then make a ContainerMap that uses Container[T]#C for the key and Container[T]#AB for the value...

:: atomly ::

[ ato...@atomly.com : www.atomly.com  : http://blog.atomly.com/ ...
[ atomiq records : new york city : +1.347.692.8661 ...
[ e-mail atomly-new...@atomly.com for atomly info and updates ...


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

ScottC

unread,
Nov 22, 2013, 10:06:37 PM11/22/13
to scala...@googlegroups.com
It is incredibly dangerous to cache data with a type reference as a key.

1.  A type is not unique in the sense you might expect, the same fully qualified type name can appear multiple times, once for each class-loader.  Beware.
2.  Holding on to a reference of a type prevents its corresponding class-loader from garbage collecting, which can lead to memory leaks.

Be very cautious with this type of code.

If you are running with JRE 7 or later, leverage  ClassValue, which is the solution to these problems in the same way that caching data per thread requires ThreadLocal because manually caching by thread is dangerous.
http://docs.oracle.com/javase/7/docs/api/java/lang/ClassValue.html

If you are stuck on JRE 6, the best you can do is use a WeakReference[Class[T]] key, so that the map does not cause the static class data or classloader to leak when no longer needed.

shvfn

unread,
Nov 23, 2013, 4:12:50 AM11/23/13
to scala...@googlegroups.com
Hi atomly,

thanks for the idea! I tried it like this:

class ComponentCollection[T]() extends ArrayBuffer[T] {

    type C = Class[T]
    type AB = ArrayBuffer[T]
}

[...]
class EntityManager {

val collectionMap : Map[Class[ComponentCollection[_]#C],ComponentCollection[_]]
  
    def addComponent[T]( eId: Int, comp: T )( implicit m: Manifest[T] ) {
       
        val cc = new ComponentCollection[T]()                 // Do I have to instantiate this?
        if ( !collectionMap.contains( classOf[cc.C] ) ) {           
            collectionMap += classOf[cc.C] -> cc              
        }
        collectionMap(classOf[cc.C])(eId) = comp    // <- still type mismatch
    }

[...]

But sadly that does not work either.
Using ComponentCollection[T]#AB as value didn't work, because I wasn't able to create an instance of that type?!
Perhaps I did't get it right? I'm not sure.

The problem is to tell the compiler about the relation between key and value... I also tried:   

val collectionMap : Map[Class[[T]],ArrayBuffer[T]] forSome { type T <: Component}

because this expresses the relation, but that leads to other problems.

ScottC, thanks for your warning, but to be honest, I have no idea how to use ClassValue here? :]

Perhaps it's not possible to solve this in the way I suggested. I'll look out for other solutions!



Sonnenschein

unread,
Nov 24, 2013, 11:41:01 AM11/24/13
to scala...@googlegroups.com
Stefan, you could possibly enhance the follwong design to your needs:

import scala.collection.mutable.Map
import scala.reflect.ClassTag

class ClassMap[T: ClassTag] {
  protected def key[V <: T](value: V)(implicit classTag: ClassTag[V]): Class[_] =
      classTag.runtimeClass
  protected val map = Map.empty[Class[_], T]

  def add(value: T) = map put (key(value), value)
  def get(value: T) = map get (key(value))
}

// usage:
val cm = new ClassMap[Int]
cm.add(1)

Did I get your requirement right?
Peter

Sonnenschein

unread,
Nov 24, 2013, 12:14:32 PM11/24/13
to scala...@googlegroups.com
Sorry, let me correct the method signatures:

class ClassMap[T: ClassTag] {
  protected def key[V <: T](value: V)(implicit classTag: ClassTag[V]): Class[_] =
      classTag.runtimeClass
  protected val map = Map.empty[Class[_], T]

  def add[V <: T: ClassTag](value: V) = map put (key[V](value), value)
  def get[V <: T: ClassTag](value: V) = map get (key[V](value))
 
  override def toString = map.toString
}

shvfn

unread,
Nov 26, 2013, 7:08:34 AM11/26/13
to scala...@googlegroups.com
Hi Peter,

thanks for your suggestion! It does not really meet my requirement, but I guess what I wanted to do, could not work in the first place... I now found a different (and simple) solution to achieve what I needed.

Anyway, thanks again!

Stefan

Paul Keeble

unread,
Nov 26, 2013, 8:38:54 AM11/26/13
to scala-user

What is the solution you found? I have a similar problem and keen to know how you chose to solve it.

--

shvfn

unread,
Nov 26, 2013, 9:28:54 AM11/26/13
to scala...@googlegroups.com
Hi Paul,

I needed this for a simple entity/component-system (similar to Artemis-EntitySystem). Entities are represented by integers and their components are simply stored at the entities index in the component-arrays.

I use this now:

    var typeIndexSequence = 0
    val typeMap = Map.empty[Class[_], Int]
    val componentBuffer = new GBuffer[GBuffer[_ <: Component]]
  
    [...]
   
    /**
     * link the component with the entityId by adding it to the componentArray at index eId
     */
    def setComponent[T <: Component]( eId: Int, comp: T ) {

        val compClass = comp.getClass()
        val typeIndex = if ( !typeMap.contains( compClass ) ) {
            addNewComponentType( compClass )
        }
        else {
            typeMap( compClass )
        }

        componentBuffer( typeIndex ).asInstanceOf[GBuffer[T]]( eId ) = comp

        // add the typeIndex of the component to the entities' componentBitSet
//        entity2Components( eId ) += typeIndex
    }

    /**
     * return the component from its' componentArray at index eId
     */
    def getComponent[T]( eId: Int, compClass: Class[T] ) = {
        componentBuffer( typeMap( compClass ) ).asInstanceOf[GBuffer[T]]( eId )
    }

    [...]
   
    /**
     * add the class with a new typeindex to the typeMap, init a new componentBuffer at the typeindex
     */
    def addNewComponentType[T <: Component]( clzz: Class[T] ): Int = {
        val typeIndex = typeIndexSequence
        typeIndexSequence += 1
        typeMap += clzz -> typeIndex
        componentBuffer += new GBuffer[T]
        typeIndex
    }



A GBuffer is an ArrayBuffer that grows automatically, so this doesn't throw an Exception:

val gb = new GBuffer[String]
gb(100) = "test"                 // would normally throw IndexOutOfBoundsException

So, this is simple, but ok for me... :)

Best,
Stefan
Reply all
Reply to author
Forward
0 new messages