Vortex Studio SDK Advanced - Key Frames



Basic Concepts

What Is a Key Frame?

A key frame is a capture of all application content data at a certain point in time that allows the application to resume the simulation from that point. It contains all content extensions' input, output, 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.

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, query which ones exist. It is also used to set global parameters on the 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.

D:\STUDENTS KEY FRAMES
├───Excavator_Student1
│       08dd0f25-e660-4cbd-a47d-11e68e20c4a3
│       09683d2c-8d25-4d5e-9129-bb55bf0dde15
│       389f39df-e6d6-4de8-b51d-539f54121811
│       4b427f0d-0864-4080-a966-0ada42cb7d04
│       b0bc9655-d432-48c3-9b51-bb487989c5bd
│       index
│
├───Excavator_Student2
│       1595455c-d1e9-41a7-b2e4-db97784d4282
│       2cc89a62-81f8-451d-b5b6-d686e30c8df0
│       39514cfc-08a8-46c1-8606-bb7d4cfa85e4
│       7307de35-8ef7-429c-bbdb-057ee38ca300
│       962a1569-757a-4107-858c-93a7391bd406
│       index
│
└───Excavator_Student3
        05e38171-d34c-4464-a380-da1aee9da3e8
        97ac39cc-e390-4d8a-a384-434d2c82181e
        f6c8d96b-fa31-4f09-9bf6-6ca60621ad39
        index

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 won't 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 applied at the time the key frame was taken, must be applied when the key frame is restored.

Using Key Frames in Your Vortex C++ Application

All key frame functionalites are 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 of list of keyframe: it can create, apply and delete keyframes.

Using the KeyFrameManager API

The VxContent::KeyFrameManager is the central access for KeyFrame functionalities.  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 KeyFrameList.The API allows to create, delete, and query all KeyFrameList that are available. KeyFrameList management methods are all asynchronous: they return immediately but the actual operation will be performed at a safe time.

The main use is to create a KeyFrameList.

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

Some of those 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);
}

KeyFrame 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 KeyFrames that are created in persisted KeyFrameLists. It allows to reuse the KeyFrames in another session, under the condition that the same content that was used for creating the KeyFrame 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 KeyFrame storage path. A custom object must be subclassed from VxContent::KeyFrameManager::Listener and registered into the KeyFrameManager. The overriden virtual functions will be call when it is necessary.

KeyFrame restored Listener

A listener can be registered in the KeyFrameManager to be notified when a KeyFrame was restored. A KeyFrameRestoredCallback must be implementated 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 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 keyframe data. The KeyFrameList can be used from any node in the network. It is used 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 you 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 Listenerclass provides the following callbacks:

// 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::VxExtension have all the InputOutput and Parameter values saved automatically by the Vortex key frame. Those values are saved and restored during 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.

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

Python 3 Support

Vortex Dynamics Script extensions 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 Vorteximport *
 
def post_step(extension):
   extension.mInternalValue += 1
 
""" Called after the keyframe has been taken.
    It is possible to modify the provided data parameter, which is an empty 
    dictionary and store values that will be provided back in the on_keyframe_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_keyframe_save(extension, data):
   data['myInternalValue'] = extension.mInternalValue
 
""" Called after the keyframe has been fully restored. Data is a dictionary filled with the values that were provided in the corresponding on_keyframe_save.""" 
def on_keyframe_restore(extension, data):
   extension.mInternalValue = data['myInternalValue']

Python 2 Support

Vortex Python extensions 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 keyframe has been taken.
    It is possible to modify the provided data parameter, which is an empty 
    dictionary 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 dictionary filled with the values that were provided in the corresponding on_state_save.""" 
def on_state_restore(self, data):
   self.mInternalValue = data['myInternalValue']

Tutorials

See tutorial MechanismViewer 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.