Skip to content

Lifetime Management

ryobg edited this page Feb 15, 2018 · 16 revisions

Intro

The Lifetime management functionality allows you change the way JC manages an object lifetime. By default, for the purposes of cleaning garbage (and to prevent a save file pollution), an object which is not referenced directly or indirectly by JDB or JFormDB gets destroyed after around 10 seconds.

It is not an obligation to use this functionality and you may not need it at all, but consider the following:

Use-cases. Why you may need to use and understand lifetime management functionality

A function execution takes too much time.

Thus an object created in the beginning of the function execution have a high chance of being destroyed before the execution completes:

int function myFunc(Actor[] actors)
   int o = JArray.object()
   int i = actors.Length
   while i > 0
     i -= 1
     -- Some long-running operation. Note that it multiplies by the number of actors even it 
     Utility.Wait(4.0)
     JArray.addForm(o, actors[i])
   endwhile
   -- o, where are thou? o? o!
   return o
endfunction

Use of retain and release techniques (or pools) helps to avoid such issues.

A function gets called very often and it creates plenty of unused objects.

Due to 10 seconds lifetime guarantee applied to newly created objects, the objects created by that function are forced to persist for that period of time. Thus it is desirable to help JC and tell it that the objects are not needed any more (with the JValue.zeroLifetime function):

int function getKeyIndex(int map, string key) global
   -- initially `keysArray` array lifetime set to 10 sec 
   int keysArray = JMap.allKeys(map)
   int index = JArray.findStr(keysArray, key)
   -- reduces `keysArray` lifetime to minimum
   JValue.zeroLifetime(keysArray)
   return index
endfunction

Yo do not want anyone to access your information

Either you intentionally do not want anyone to access your information, or you are avoiding use of JDB and JFormDB (though this is still slightly absurd as other mod authors will still be able to access the forms via the Game.GetForm function).

The Main part

Each time when a script creates new string or Papyrus array, Skyrim allocates memory and automatically frees it when nothing is using that string or array anymore. JC is an alien, not a native part of the engine, so the engine knows nothing about an objects created by JC and can not manage their lifetime and memory.

The lifetime management model is based on object ownership. Any container object may have zero, one or more owners. As long as an object has at least one owner, it continues to exist. If an object has no owners - it gets destroyed (this is your average reference counting scheme for these who are familiar with the concept).

The objects lifetime is managed by calling the following functions (all of them are declared in the JValue script):

Retain and release functions

int function retain(int object, string tag="")
int function release(int object)
function releaseObjectsWithTag(string tag)

JContainers uses simple owner counting (reference counting). Each object has a counter. When an object gets inserted into another container or JValue.retain is called on that object, the object counter increases by 1. When the object gets removed from a container or released via JValue.release, the reference counter decreases by 1.

JC temporarily (for around 10 seconds) prolongs the lifetime, i.e. owns an object preventing the object destruction, in the following cases:

  • When a newly created object gets returned to Papyrus. For instance objects created with the object, objectWith*, all/Keys/Values or readFromFile functions.
  • An object A has no owners except another object B. When B gets destroyed, A gets released and as its reference count reaches zero - JC will prolong A's lifetime.

JC stops to temporarily own an object if the user invokes JValue.release or JValue.releaseAndRetain on that object - in this way the user explicitly tells that this object is not needed anymore.

Important

The caller of JValue.retain is responsible for releasing an object. Not released objects will remain in save file forever. The worst thing is that such object may contain lot of another objects and the objects may also contain objects, thus whole graph of objects will hang in a memory and in a save file.

Illustration of reference counting: Illustration

In the above retain and releaseObjectsWithTag functions, you'll notice a string parameter called the "tag". A tag is a unique string that identifies objects. You could use your mod's name as a tag. Passing in "" as a tag removes the tag from an object. Tags are useful to help prevent forgetting to release an object. Or, Papyrus may throw an error in between calls to retain and release for a given object, thus preventing that object from ever being released. By tagging an object, it's extremely easy to release it at any time by releasing all objects with that tag by calling JValue.releaseObjectsWithTag.

Important

JValue.releaseObjectsWithTag complements all retain calls with release that were ever made to all objects with given tag. Field of use of the function is mostly maintenance as the function can be slow - the function iterates overs all objects, releases the ones with matching tag.

  • The JValue.releaseAndRetain function:
int function releaseAndRetain(int previousObject, int newObject, string tag="")

It is just a union of retain and release calls. Releases a previousObject, retains, tags and returns newObject. Typical usage:

-- create and retain an object
self.followers = JArray.object()
-- release object
self.followers = 0
-- or replace with another array
self.followers = JArray.object()

int property followers hidden
    int function get()
        return _followers
    endFunction
    function set(int value)
        _followers = JValue.releaseAndRetain(_followers, value, "<uniqueTag>")
    endFunction
endProperty
    
int _followers = 0
  • The JValue.zeroLifetime function:
int function zeroLifetime(int object)

Use case described above. If JC temporarily owns an object - the function reduces the object lifetime to a minimum and it returns the object. This function does not release the object.

Pools

int function addToPool(int object, string poolName) global native
function cleanPool(string poolName) global native

A Pool is a timer-saver functionality, a container. Pools are handy when you need to retain (and then release) some big group of objects without having to release all the objects manually. The poolName parameter is a unique string that identifies that pool. Internally the Pool is JArray.

  • The function addToPool adds the object parameter into underlying array, retaining it.
  • cleanPool clears the array, releasing its contents.

Important

Pools are global. Pools with equal names refer to the same pool.

int tempMap = JValue.addToPool(JMap.object(), "uniquePoolName")
int tempArray = JValue.addToPool(JValue.readFromFile("array.json"), "uniquePoolName")
-- in any function later:
JValue.cleanPool("uniquePoolName")

Hand-made Pool could look like this:

int pool = JDB.solveObj(".pools.myPool")
if !pool
  pool = JArray.object()
  JDB.solveObjSetter(".pools.myPool", pool, true)
endif

int tempMap = JMap.object()
JArray.addObj(pool, tempMap)
int tempArray = JValue.readFromFile("array.json")
JArray.addObj(pool, tempArray)
-- in any function later:
JDB.solveObjSetter(".pools.myPool", 0)