NET:Concepts:Frame Locking

From Lavish Software Wiki
Revision as of 17:06, 19 October 2007 by Lax (talk | contribs) (→‎Behavior)
Jump to navigation Jump to search

Overview

Frame locking is a term used to describe the act of synchronizing with the host application's main thread. Generally, this means a game's rendering loop.

Behavior

Frame locking is implemented in Inner Space as a reader-writer lock. Any number of .NET application threads may obtain any number of locks overall or per thread. There is no harm in locking multiple times in a single thread, and no harm in multiple threads locking at once.

The host's thread will always attempt to continue as long as the reader count is zero. If a reader is waiting, the host will release its writer lock and wait for the readers to leave. As soon as all readers have left, the host will re-acquire the writer lock and repeat the process.

Acquiring frame lock

Acquiring frame lock may require the host application to complete its processing of the current frame, because the host will not always be ready when you are. That is, unless the frame is already locked, and the host thread is waiting for the number of readers (your locks) to reach zero. Frame lock is acquired after, and only after, that state is reached. Each time you acquire a lock, the reader count is incremented.

Unlocking

Each time you unlock, the reader count for the current thread is decremented. As soon as the count for all threads is zero, the host application is signaled to continue, and may perform any of its necessary duties until the following frame.

Implicit frame locks

All LavishScript and most Inner Space .NET API functions implicitly lock and unlock the frame.

Using Non-persistent LavishScript Objects

Object persistence, in terms of LavishScript Objects, is the ability to transcend the bounds of frame lock. Persistent LavishScript objects may be referenced and used from one frame to the next. However, non-persistent LavishScript objects are garbage collected (by LavishScript) when the frame ends. This results in disposal of the reference .NET is holding, which means that checking the IsValid property of a LavishScriptObject will return false -- the object no longer has any relevance to LavishScript, and therefore no members or methods of that LavishScriptObject will yield useful results.

To reuse an object reference safely, the object should either be persistent, or only used within a frame locked segment.

Exclusive Locks

Exclusive locking blocks any other thread attempting to acquire frame lock, until the exclusive lock is released -- it does not suspend execution of the other threads, only blocks frame lock attempts. This generally should not be used, as it may deadlock the application if the exclusive locking thread must wait on a resource in use in another thread that attempts a frame lock. Standard thread synchronization should be used instead.

Frame wait

A frame wait will cause a thread to yield execution until the following frame. This can be used instead of System.Threading.Sleep(0) (or Sleep(1) as many tend to use) to indicate the shortest yield possible, only continuing when the current frame has ended and the next begun. Frame lock can be implicitly acquired during frame wait.

Frame Locking API

Frame locking API in .NET is implemented in LavishVMAPI.Frame. Equivalent functions with the same names as those in LavishVMAPI.Frame are found in ISInterface for Inner Space extensions.

Commentary

Source: Lax (http://www.isxwow.net/forums/viewtopic.php?f=15&t=791&p=5247&hilit=Frame.Lock#p5247])

The reason it goes slower without explicit frame locking in your code, is that without it, you're relying 100% on the 
API to perform the frame locking for you.

It has to be done in order to ensure synchronization with the game, so the API does it internally on a per-call basis. The 
problem with relying on the API to  do it for you, is that consecutive function calls may take place on the following frame, 
instead of the current one. Without frame locking, 60 individual function calls may end up taking 60 individual frames, with 
an elapsed time of 1 second at 60 fps, instead of all being done in one frame, with an elapsed time counted in milliseconds 
or better (even if the function calls are consecutive). To improve efficiency, you want to strategically lock when doing sections 
of calls that should be in the same frame.

A lot of people seem to want to avoid frame locking, with the belief that it's going to hurt their game performance. To avoid 
it, they cache a lot of data in a frame lock, and make use of it outside of frame lock, entering back into frame lock when the 
game must again be manipulated (e.g. to perform an action in the game). To some degree, this is a great idea. But, probably not 
for the right reason. Locking and unlocking is not going to alter performance very much, and doesn't slow down the game. 

Locking just means that your thread is going to wait for the game to be ready for you, not blocking the game wherever it happens 
to be (thusly why n individual calls may end up taking n frames). The real performance hurt comes from reusing the same LavishScript 
API calls to obtain data, regardless of frame locking. In other words, if you have to keep using Me.X, it is helpful to cache it in 
.NET because this removes LavishScript parsing as well as the managed to unmanaged to managed marshaling and context changes. But it 
doesn't help so much to cache rarely used but constantly changing data (because then you're still doing the hard work, but perhaps 
for nothing). In other words, optimizing out additional LavishScript calls is much more beneficial than optimizing out an additional 
frame lock, any way you look at it.

See Also