Vortex Studio SDK: Advanced - Key Frames

Topics

Basic Concepts

What Is a Key Frame?

A key frame is a capture of all application content data at a certain point in time and allows the application to resume the simulation from that point. It contains all content extensions' input, output, and parameter values, and supplementary internal data that gives the key frame a reference in time. This reference is expressed in terms of the frame number, the simulation time at the moment the key frame was captured. Key frames are aggregated in a key frame list.

Note
This feature is not available for Vortex® Studio Academic edition. Please see Feature Set Comparison for more information.

Key Frame Manager

The Key Frame functionality is available from the ApplicationContext, which can be accessed from the VxSim::VxApplication directly or from any extension. The VxContent::KeyFrameManager is used primarily to manage key frame lists: create them, delete them, and query which ones exist. It is also used to set global parameters for key frame functionality, and it offers a listener class allowing the user to be notified when there is a change in the key frames.

Key Frame Lists

A key frame list is a collection of related key frames grouped for convenience (e.g., by scenario, user, session). It is through key frame lists that the user saves and restores key frames. The list and its associated key frames can either exist in memory or be saved to a file on disk for later use.

The key frame list class also exposes a listener class easily allowing users to be called back when the key frame list changes. All list operations are performed asynchronously by the key frame manager which ensures that all such operations will minimize the impact on the performance of the simulation (e.g., no stutters when saving a key frame).

All persisted key frame lists (and their associated key frames) are saved to a global destination folder which can be configured through the key frame manager API VxContent::KeyFrameManager::setGlobalPath. That folder contains one sub-folder per key frame list. The following is an example folder structure.

Key Frame Usage Guidelines

  • When restoring a key frame, the same content that was loaded when it was captured must be loaded when it is restored.
  • If the content that was captured is only partially loaded during the restore, the missing content will not be automatically loaded and thus will only be partially restored.
  • If the content was modified between the capture and the restore, the restore may not be able to load the entire modified content.
  • The same configurations which were activated at the time the key frame was taken must be activated before the key frame is restored.

Using Key Frames in Your Vortex C++ Application

All key frame functionality is centralized in two classes: VxContent::KeyFrameManager and VxContent::KeyFrameList.

VxContent::KeyFrameManager is obtained from the VxSim::ApplicationContext and is used for general settings of the KeyFrame functionality. VxContent::KeyFrameList manages the list of key frames: it can create, apply and delete key frames.

Using the KeyFrameManager API

The VxContent::KeyFrameManager is the central access for key frame functionality.

VxContent::KeyFrameManager has the following responsibilities:

  • Query, create or delete instances of VxContent::KeyFrameList.
  • Set or query the storage path of all key frames.
  • Notify the use of any changes in the key frame.

KeyFrameList Management

Globally, KeyFrameManager manages all KeyFrameLists.The API allows to create, delete, and query all KeyFrameLists that are available. KeyFrameList management methods are all asynchronous: they return immediately but the actual operation will be performed at a safe time.

Create a KeyFrameList object
Vx::VxSmartPtr<VxContent::KeyFrameList> keyFrameList = getApplicationContext().getKeyFrameManager()->createKeyFrameList("MyList", true);

Some of these methods require a custom callback function to be passed with one the following signatures:

typedef std::function < void(VxContent::KeyFrameManager::eErrorCode code, const std::vector<std::string>&) > GetListsResultFunction;
typedef std::function < void(VxContent::KeyFrameManager::eErrorCode code, const std::string&) > RemoveListResultFunction;

Consider the following example:

bool onGetListResponseCalled = false;
std::vector<std::string> lists;

auto onGetListResponseCallback = [&onGetListResponseCalled, &lists](VxContent::KeyFrameManager::eErrorCode code, const std::vector<std::string>& listNames)
{
	if (code == kNoError)
	{
		lists = listNames;
	}
	onGetListResponseCalled = true;
};

mApplication->getContext().getKeyFrameManager()->getKeyFramesLists(onGetListResponseCallback );
while ( !onGetListResponseCalled )
{
	mApplication->update();
}

// Will remove all lists from the key frame manager.
for (auto it = lists.begin(); it != lists.end(); ++it)
{
	mApplication->getContext().getKeyFrameManager()->removeKeyFramesList(*it);
}

Key Frame Storage Path

The KeyFrameManager can be used to set the global key frames directory. It is the directory that will be used to serialize the key frames that are created in persistent KeyFrameLists. The directory allows the reuse of key frames in another session, under the condition that the same content that was used for creating the key frame has been loaded.

KeyFrameManager Listener

A listener can be registered in the KeyFrameManager to be notified of the creation of a KeyFrameList, and of the modification of the key frame storage path. A custom object must be subclassed from VxContent::KeyFrameManager::Listener and registered into the KeyFrameManager. The overriden virtual functions will be called when necessary.

Key Frame Restored Listener

A listener can be registered in the KeyFrameManager to be notified when a key frame was restored. A KeyFrameRestoredCallback must be implemented and given to VxContent::KeyFrameManager::registerKeyFrameRestoredListener. Every node on the simulator will get notified that a key frame was restored. The returned listener is a reference counter object that must be kept for the callback to be called. When the callback is no longer required, call unregister() on the listener.

Using the KeyFrameList API

The VxContent::KeyFrameList class is used to create, restore, persist and manage lists of key frames. It offers thread-safe access to the key frame data. The KeyFrameList can be used from any node in the network, to save, restore and query key frames on that list.

A key frame list has a status (of type VxContent::KeyFrameList::eStatus) which can be queried with VxContent::KeyFrameList::getStatus. After the constructor of a new list has been called, the list is in the kNotInitialized status and must not be used before it has been fully initialized (see the section below about how your code can be called back when the list is initialized).

KeyFrameList status enum values
enum eStatus
{
	/// The KeyFrameList is not initialized, initial status
	///
	kNotInitialized,

	/// The KeyFrameList is initialized
	///
	kInitialized,

	/// The save path was changed, this list is invalid and cannot be used anymore.
	///
	kGlobalKeyFrameListsPathChanged,

	/// The global key frame lists path is invalid
	/// kInvalidGlobalKeyFrameListsPath,


	/// This list was removed, this object is invalid.
	///
	kRemoved
};

Basic Usage: Saving and Restoring a Key Frame

The two basic properties of a key frame list that cannot be changed after its creation are its name and whether the list and its key frames will be saved to disk.

After object creation, you can save a new key frame through the VxContent::KeyFrameList::saveKeyFrame method. The method returns the VxSim::VxUuid of the key frame to be saved (recall that the operation is asynchronous).

After the key frame is saved (see below to see how your application can precisely know when the key frame is saved), you can use the ID to retrieve the created key frame object and pass that object to the VxContent::KeyFrameList::restore to restore the simulation to that point.

Example:

// Create a list that will contain the key frames. Key frames are persisted to the file
Vx::VxSmartPtr< VxContent::KeyFrameList > keyFrameList = application->getContext().getKeyFrameManager()->createKeyFrameList("basic_usage_list", true);

...
// Other code that waits for the list to be initialized (see example below).
...

// Save a key frame
VxSim::VxUuid frameId = keyFrameList->saveKeyFrame();

// Simulate for few seconds.
for(int i = 0; i < 200; i++)
	application->update();

// Restore this key frame
keyFrameList->restore(keyFrameList->getKeyFrame(frameId));

Manipulating the Key Frames in a List

The key frame list API allows you to easily get an array of all key frames in the list, remove a key frame from the list and clear the list of all its key frames.

Example:

// Get the list of key frames in a key frame list.
Vx::VxArray<KeyFrame> keyFrames = keyFrameList->getKeyFrames();

// Remove the first key frame
keyFrameList->remove(keyFrames[0]);

// Empty the list, deletes all key frames, but keeps the list.
keyFrameList->clear();

Getting Called Back When a Key Frame List Changes

As previously mentioned, many key frame list operations are performed asynchronously. Those functions return immediately and the operations are not performed yet. To be notified when such operations or changes to the list are completed, you must subclass and register an instance of a subclass of VxContent::KeyFrameList::Listener.

The Listener class provides the following callbacks (see the Vortex Technical Documentation for details):

// New key frame was saved.
virtual void onSaved(VxContent::KeyFrameManager::eErrorCode code, const KeyFrame& keyFrame) = 0;

// A key frame was restored.
virtual void onRestored(VxContent::KeyFrameManager::eErrorCode code, const KeyFrame& keyFrame) = 0;

// A key frame was removed from the list.
virtual void onRemoved(VxContent::KeyFrameManager::eErrorCode code, const KeyFrame& keyFrame) = 0;

// The key frame list was cleared.
virtual void onCleared() = 0;

// The status of the list has changed.
virtual void onStateChange(VxContent::KeyFrameList::eStatus) = 0;

After you have defined your own subclass of VxContent::KeyFrameList::Listener and implemented the methods you need, you can register or unregister the listener using the appropriate key frame list method.

void registerListener(Listener* listener);
void unregisterListener(Listener* listener);

The following is an example that demonstrates a sample VxContent::KeyFrameList::Listener subclass implementation:

class KeyFrameListListener : public VxContent::KeyFrameList::Listener
{
public:
	KeyFrameListListener(MyObject* owner)
		: mOwner(owner)
	{
	}

	virtual void onRestored(VxContent::KeyFrameManager::eErrorCode code, const VxContent::KeyFrame& keyFrame)
	{
		if (code == VxContent::KeyFrameManager::kNoError)
		{
			mOwner->keyFrameRestored(keyFrame);
		}
	}

	virtual void onSaved(VxContent::KeyFrameManager::eErrorCode code, const VxContent::KeyFrame& keyFrame)
	{
		if (code == VxContent::KeyFrameManager::kNoError)
		{
			mOwner->keyFrameSaved(keyFrame);
		}
	}

	virtual void onRemoved(VxContent::KeyFrameManager::eErrorCode code, const VxContent::KeyFrame& keyFrame)
	{
		if (code == VxContent::KeyFrameManager::kNoError)
		{
			mOwner->keyFrameRemoved(keyFrame);
		}
	}

	virtual void onCleared()
	{
		mOwner->keyFrameListCleared();
	}

	virtual void onStateChange(VxContent::KeyFrameList::eStatus status)
	{
		if (status == VxContent::KeyFrameList::kInitialized)
		{
			mOwner->setIsListReady(true);
		}
		else if (status == VxContent::KeyFrameList::kNotInitialized)
		{
			mOwner->setIsListReady(false);
		}
	}

	private:
		MyObject* mOwner;
	};

class MyObject
{
public:
	void keyFrameRestored(const VxContent::KeyFrame& keyFrame);
	void keyFrameSaved(const VxContent::KeyFrame& keyFrame);
	void keyFrameRemoved(const VxContent::KeyFrame& keyFrame);
	void keyFrameListCleared();
	void setsetIsListReady(bool ready);

private:
	Vx::VxSmartPtr<VxContent::KeyFrameList> mKeyFrameList;
	Vx::VxSmartPtr<KeyFrameListListener> mListener;
};

MyObject::MyObject()
{
	mKeyFrameList = new VxContent::KeyFrameList("list_with_listener", false);
	mListener = new KeyFrameListListener(this);
	mKeyFrameList ->registerListener(mListener.get());
}

Add Key Frame Support to Your VxSim::VxExtension

All VxSim::VxExtensions have all the input, output and parameter values saved automatically by the Vortex key frame. Those values are saved and restored before the VxApplication update. If this is all you need for one of your extensions, you do not need to modify it further. However, if you must save or restore additional internal data, the IExtension interface now exposes two methods which you can override in your extension to perform those tasks.

To save additional internal data, override IExtension::onStateSave and IExtension::onStateRestore to save and restore internal data using the data stream in argument. You can also override IExtension::onStateRestore if you need to recalculate internal values based on restored field values.

Note
onStateSave/onStateRestore will only be called on the Master Node. If you need to be notified that a key frame was restored, consider registering a KeyFrameRestoredCallback on the Key Frame Manager.

The extension's field values are saved or restored before those two callbacks are called so those values can be safely accessed inside the callbacks if needed.

onStateSave/onStateRestore Guidelines

As described before, most key frame list operations are dispatched to another thread for better performance. Because of this, your implementations of IExtension::onStateSave and IExtension::onStateRestore must adhere to the following guidelines.

  • Must be fast: it must not slow down the simulation.
  • Thread safe: the key frame capture is multi-threaded.
  • No need to save/restore input/output/parameters fields.
  • Implementation only needs to handle internal data.

The following is a code example demonstrating the use of those callback functions.

class UserExtension : public VxSim::IExtension
{
public:
	UserExtension ::VxExtension* iProxy)
		: VxSim::IExtension(iProxy)
		, iInput("Hello World!", "StaticInput", &iProxy->getInputContainer())
		, oOutput(12, "StaticOutput", &iProxy->getOutputContainer())
		, pParameter(0.234, "StaticParameter", &iProxy->getParameterContainer())
		, mInternalValue(999)
		, mValueDependingOnParameter(pParameter.getValue() * 2)
	{}
	virtual void onStateSave(std::ostream& oStream)
	{
		oStream.write(reinterpret_cast<char*>(&mInternalValue), sizeof(int));
	}
	virtual void onStateRestore(std::istream& iStream)
	{
		iStream.read(reinterpret_cast<char*>(&mInternalValue), sizeof(int));
		mValueDependingOnParameter = pParameter.getValue() * 2;
	}
};

Python Extensions Support

Vortex Python extensions now support methods equivalent to IExtension::onStateSave and IExtension::onStateRestore that allow the user to save, restore, and recalculate internal data and values. Internal data are saved in a standard Python dictionary for easy access.

from VxSim import *

def post_step(self):
	self.mInternalValue += 1

""" Called after the key frame has been taken.
	It is possible to modify the provided data parameter, which is an empty
	dictionnary and store values that will be provided back in the on_state_restore.
	The following types are supported: booleans, integers, long integers, floating point
	numbers, complex numbers, strings, Unicode objects, tuples, lists, sets, frozensets,
	dictionaries, and code objects, where it should be understood that tuples, lists, sets,
	frozen sets and dictionaries are only supported as long as the values contained therein are
	themselves supported; and recursive lists, sets and dictionaries should not be written (they will cause infinite loops).
	The singletons None, Ellipsis and StopIteration can also be saved and restored."""
def on_state_save(self, data):
	data['myInternalValue'] = self.mInternalValue

""" Called after the keyframe has been fully restored. Data is a dictionnary filled with the values that were provided in the corresponding on_state_save."""
def on_state_restore(self, data):
	self.mInternalValue = data['myInternalValue']

Tutorials

Refer to the Mechanism Viewer tutorial to see an example of how the ApplicationKeyFrameKeyboardListener class saves and restores key frames, and moves through a list of key frames depending on keyboard events.

 

Next topic: Advanced - Events