1

I'm writing a JRPG style game at the moment, and am defining my items/enemies etc. in YAML files. Rather than load them at runtime (which is proving to be a pain in Scala, especially on Android) I decided to pre-compile them into a Scala object as lazy values.

My only worry is that eventually, as these values are accessed, the object will begin taking up more memory than is really needed.

Is there anyway to reinitialize a Scala object or to clear the lazy values back to their default state? Alternatively, is there a better way to accomplish what I'm trying to do here?

svick
  • 236,525
  • 50
  • 385
  • 514
Mistodon
  • 627
  • 1
  • 5
  • 12

5 Answers5

8

I find soft (not weak) references very handy for this. Weak references get eaten every GC they're not needed, which can waste a lot of effort if they're repeatedly accessed. Soft references are only eaten when there's memory pressure (which formally may be every GC but at least the JVM can exercise a bit of discretion). Anyway, for use with Scala, this is very handy:

class Soft[T,U](t: T)(gen: T => U) {
  private[this] var cache = new java.lang.ref.SoftReference(gen(t))
  def apply(): U = {
    var u = cache.get()
    if (u==null) {
      u = gen(t)
      cache = new java.lang.ref.SoftReference(u)
    }
    u
  }
}
object Soft {
  def apply[T,U](t: T)(gen: T => U) = new Soft(t)(gen)
}

Now you wrap an appropriate amount of stuff in a Soft, and when you want it you use () to get the data:

val soft = Soft(10)(n => Array.tabulate(n)(i => i*i*i))
soft()(3)   // 27

There is a not-entirely-negligible penalty to get a soft reference (usually equal to a couple of object creations), so if you're going to use something heavily, grab it first and then do the work:

val arr = soft()
// Intensive operations with arr
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
2

There is no such mechanism. Once a lazy val is accessed and its initializer is evaluated, the resulting value is held in a field of the instance of which it is a part like any other and only that instance becoming garbage itself will relinquish the reference to that "lazy" value and (possibly) allow it to be reclaimed as well. Naturally, if there are other references to it, they will preclude its being reclaimed.

Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
1

I think that there's no built-in mechanism for it, so you'll need to implement it yourself. The simplest solution would be to have some form of a cache with expiry after certain time passes - for example, as described in Java time-based map/cache with expiring keys.

Community
  • 1
  • 1
Rogach
  • 26,050
  • 21
  • 93
  • 172
1

It depends if you have - let's call them "weak values" - for different types or weak values of one type but many objects involved. If you have the latter I'd go with the solution by Rogach using the map/cache.

However if you have the first - different classes or maybe a few big objects - one approach is surely to use WeakReference.

This is a solution I came up with - maybe in future this could be done with macros:

object UseWeakRef extends App {

  import scala.ref.WeakReference

  class WeakRefCreator[T <: AnyRef] {
    private var weakRef: WeakReference[T] = WeakReference(null.asInstanceOf[T])
    def apply(creator: => T): T = weakRef.get match {
      case None =>
        val newVal: T = creator
        weakRef = WeakReference(newVal); newVal
      case Some(value) => value
    }
  }

  private val expensiveRef = new WeakRefCreator[String]
  def expensiveVal = expensiveRef {
    println("creating expensive object")
    "This is expensive"
  }

  println(expensiveVal)
  println(expensiveVal)
}

The output btw is:

creating expensive object
This is expensive
This is expensive
michael_s
  • 2,515
  • 18
  • 24
1

By using the code generation approach you will always have at least one copy of the objects in the form of code to contruct the object, and possibly another in the objects themselves. The code to construct the data is probably using more memory than the actual objects.

I suggest you ignore this memory management problem altogether for a while - don't use lazy vals or soft references or any other deallocation scheme. If you are not targeting low-memory environments (mobile devices) then you can allow the operating system to "swap out" the construction code and much of the data (data in strings and arrays of native types) you are not using at any given time.

Since you still have the original YAML files you can revert back to loading from data files during the polish/optimization stage of game production if you discover that this part of the game is using too much memory.

Dobes Vandermeer
  • 8,463
  • 5
  • 43
  • 46