Lifetimes
Many ScriptEase API calls such as seGetString, seGetBuffer, seGetObject, and seMakeObject return handles to a ScriptEase items. These handles remain valid for a specific amount of time, the handle’s lifetime. In the API manual chapter in the descriptions of these functions, they are said to follow the standard ScriptEase lifetime model. That model is what is described in this chapter. We will use an seobject handle as an example for this chapter, but the other handles follow the same rules.
The critical concept to understand is the difference between the object itself and the handle to the object. The handle is what is returned to you when you call the API functions that follow the ScriptEase lifetime model. A handle is simply a way to refer to the underlying object. There are several API functions that control the lifetime of the handle. These functions allow you to control how long the handle will be valid. As long as the handle is valid, you can use it to refer to the object it represents in the many ScriptEase API calls that take an object as a parameter. This chapter describes the rules about how long a handle is valid and the API functions that determine this length.
The point to remember is that a handle is not the object itself but a way to refer to the object. When the handle becomes invalid, you may no longer use that handle to refer to the object. However, that does not mean the object itself will be deleted. The object is completely under the control of the ScriptEase runtime system which will delete the object when it determines that the object is no longer needed. It is quite possible that any particular object you have a handle to is also being used by some other part of the runtime system. When you delete your handle to the object, do not assume that the object will then be immediately freed. It may be but more likely the object will be retained because it is still being used elsewhere. There is no way to force any actual object on the system to be deleted.
The handles to objects and other items follow a simple set of lifetime rules. As long as the handle is valid, you may use it in any ScriptEase API call that requires an object. All handles are by default valid from the time they are returned to you from a ScriptEase API call until the current callback function you are in returns. All callback functions in ScriptEase are easily identified by having their return value enclosed in SE_CALLBACK. Common callback functions include those in the seContextParams structure, wrapper functions, and dynamic object functions.
This rule facilitates simple callback functions. Since the handle is automatically freed when the callback function returns, you do not have to explicitly free the handle. It is analogous to a C local variable to your callback function. Here is a valid sample wrapper function that uses an object handle:
SE_CALLBACK( void )
foo(secontext se,sememcount argc)
{
seobject myobj = seMakeObject(se);
sePutObject(se, SE_RETURN,SE_VALUE, myobj);
}
The purpose of this wrapper should be obvious, it creates and returns a new blank object. Here is an example of an invalid wrapper function:
static seobject myobj = NULL;
SE_CALLBACK( void )
foo(secontext se,sememcount argc)
{
if( myobj==NULL )
{
myobj = seMakeObject(se);
}
sePutObject(se, SE_RETURN,SE_VALUE, myobj);
}
The intent is clear, to create a new object and return it then keep returning that same object for any further calls to the function. But, the handle returned by seMakeObject is only valid until the end of the wrapper function so when the wrapper function returns the first time, myobj comes invalid. Trying to use that handle in later invocations will use it after it has become invalid and obviously not work. We will see how to make this example work as intended shortly.
The careful reader will be wondering what happens if you get a handle when not inside a callback function. Keeping with the C analogy, handles retrieved in a callback function are local variables and handles retrieved not in a callback function are global variables. Just as global variables last the life of a program so to do handles retrieved while not in a callback function. The handle is permanent and lasts until the secontext is destroyed and all variables and objects in it are freed.
While you may sometimes want to use a handle for your entire program, often you want to manipulate an object for a few lines of code and then be done with it. Eventually, when everyone else is done using the object, you'd like it to be freed. If you allow the handle to stay valid, you keep the object locked and it can never be freed. It will continue to use up memory until your program is done. For this reason, you may use the seFreeXXX API routines, seFreeObject in this case because we have been using seobjects in our discussion. seFreeObject tells ScriptEase that you are done using the object at that point and that your handle is to be freed then. Remember, this does not destroy the object. It only tells ScriptEase that you are no longer using the handle. As was emphasized before, ScriptEase will actually destroy the object some time in the future when it determines it is safe to do so.
To summarize the rules so far, a handle retrieved inside a callback function lasts until the handle is explicitely freed or the callback function returns, whichever comes first. A handle retrieved outside a callback function lasts until the handle is explicitly freed or the ScriptEase context is destroyed, whichever comes first.
An issue related to object lifetimes was brought up in the second wrapper function described above. How do we keep an object handle past the wrapper function it was created in? Using the C analogy, we want to turn a local variable into a global variable in the same way that the C keyword static does. The answer is that you use the seLockXXX API calls, in this case seLockObject. This call indicates that the given object handle is to be valid for the life of the program. Once you pass an seobject to this routine, it will be treated exactly like a handle returned outside of a callback function: it lasts until the handle is explicitely freed or the ScriptEase context is destroyed, whichever comes first. If a handle already is a global handle, seLockObject has no additional effect.
So, the second wrapper function written correctly is:
static seobject myobj = NULL;
SE_CALLBACK( void )
foo(secontext se,sememcount argc)
{
if( myobj==NULL )
{
myobj = seMakeObject(se);
seLockObject(se,myobj);
/* make the lock permanent
* so we can keep using it
* in every call to this
* wrapper function.
*/
}
sePutObject(se, SE_RETURN,SE_VALUE, myobj);
}
You will notice the seCloneXXX API calls such as seCloneObject. These calls take a handle, which follows the rules just described, and makes a second handle identical to the first. If the first handle was to be freed at the end of the current callback function, the clone will be freed then as well. Once created, you have two independent handles with different pointer values although they both refer to the same ScriptEase object. They both follow the lifetime rules given above independently. For instance, you might clone a regular local handle and pass it to a utility routine. You continue to use the original handle until it goes away at the end of the wrapper function. The utility routine may call seLockObject on the cloned handle and use it for a while. That is perfectly valid, both handle are independent.