-
Notifications
You must be signed in to change notification settings - Fork 24
Lifetime Management
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:
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.
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
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).
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):
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
orreadFromFile
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:
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 allretain
calls withrelease
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.
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 theobject
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)