The GFx::Value Direct Access support included in Scaleform 3.1 and higher versions is a major improvement to the way a game communicates with the AS runtime. The ActionScript communication API has been extended beyond simple types so that complex objects (and individual members within those objects) can be set and queried efficiently. For example, nested and heterogeneous data structures can now be passed back and forth from the UI to the game. For an in-depth example of using Direct Access API, please see our HUD Kit, which demonstrates the performance gains found when updating large numbers of Flash objects on a minimap.
With the Direct Access API, GFx::Values, can now store simple ActionScript types as well as references to Objects, Arrays and Display Objects. Display Objects are a special case of the Object type and correspond to entities on the stage (MovieClips, Buttons, and TextFields). The GFx::Value class API has a full set of functions for setting and getting their values and members and dealing with arrays. The GFx::Movie class API now includes methods to create objects and arrays. Please see the GFx::Value reference for the details.
The Direct Access API allows the application to bind a C++ variable (of type GFx::Value) directly to an object in ActionScript. Once this binding or reference is made, the variable can be used to easily and efficiently modify the ActionScript object. Prior to this change, users had to access AS objects through the GFx::Movie and by specifying a string path. This method incurred a performance penalty for parsing the path and finding the AS object. With direct access objects, this costly parsing and searching overhead on each call has been removed.
Many operations which were previously available only at the GFx::Movie level can now be applied directly to an ActionScript (AS) object, represented by the GFx::Value type. This results in much cleaner and more efficient code. For example, to call a method on an object called ‘foo’ located at the root, one could get a reference to the object and call Invoke on it directly, rather than use the Movie’s Invoke call.
GFx::Movie Invoke Method (less efficient):
Value ret; Value args[N]; pMovie->Invoke("_root.foo.method", &ret, args, N);
Direct Access Invoke Method (better):
(Assumes 'foo' holds a reference to an AS object at "_root.foo") Value ret; Value args[N]; bool = foo.Invoke("method", &ret, args, N);
Along with Invoke, calls to check, get and set values on AS objects can now be done through the Direct Access API, using GFx::Value::HasMember, GetMember and SetMember.
Example using GetMember to update a text field on a movieClip (stored in a GFx::Value):
Value tf; movieClip.GetMember("textField", &tf); tf.SetText("hello");
In the example above, we first get the textfield member of the movieClip object and then use the function SetText to update its text value.
Another important capability supported by the Direct Access API is the ability to create an AS object or hierarchy of objects. In this way, objects of arbitrary depth and structure can be created and managed in C++. For example, the following code snippet could be used in order to create two objects in a hierarchy and have one be considered the child of the other:
Movie* pMovie = … ; Value parent, child; pMovie->CreateObject(&parent); pMovie->CreateObject(&child); parent.SetMember("child", child);
CreateObject creates an instance of an ActionScript object. It also accepts an optional, fully qualified class name to be passed in, if an instance of a specific class type is required. For example:
Value mat, params[6]; // (set params[0..6] to matrix data) … pMovie->CreateObject(&mat, "flash.geom.Matrix", params, 6);
Consider a more complicated situation that uses both Objects and Arrays. The following creates an array with complex object elements:
// (Assuming pMovie is a GFx::Movie*): Value owner, childArr, childObj; pMovie->CreateArray(&owner); // create parent array pMovie->CreateArray(&childArr); // create child array pMovie->CreateObject(&childObj); // create child object bool = owner.SetElement(0, childArr); //set parent[0] to child array bool = owner.SetElement(1, childObj); // set parent[1]to child object … bool = foo.SetMember("owner", owner); // later, set the 'owner' variable in // object foo, to the parent object
The function GFx::Value::VisitMembers() can be used to traverse the public members of an object. The function takes an instance of an ObjectVisitor class which has a simple Visit callback that must be overridden. This function is only valid for Object types (including Array and DisplayObject). Note that you cannot use VisitMembers to introspect a class instance completely. Methods are not enumerable because they live in the prototype. To force the visibility of members and properties, use the ASSetPropFlags method in ActionScript:
e.g.: _global.ASSetPropFlags(MyClass.prototype, ["someFunc", "__get__number", "test"], 6, 1);
Properties (getter/setter) are never enumerable via VisitMembers, even with ASSetPropFlags. However, they are accessible via the Direct Access interface and returned as VT_Object. Functions are also returned as VT_Object (if they are made enumerable via ASSetPropFlags).
The Direct Access API exposes a special case of the Object type, called DisplayObject, which corresponds to entities on the stage, such as MovieClips, Buttons and TextFields. A custom DisplayInfo API is provided to access their display properties. Using the DisplayInfo API, you can easily set properties on a DisplayObject such as alpha, rotation, visibility, position, offset and scale. Although these display properties could also be set using the SetMember function, the SetDisplayInfo call is the fastest way to do this, since it directly modifies the object’s display properties. Note that both of these methods are faster than calling GFx::Movie::SetVariable which does not operate directly on the target object.
The following code utilizes the SetDisplayInfo method to change the position and rotation of a movieclip instance:
// Assumes movieClip is a GFx::Value* Value::DisplayInfo info; PointF pt(100,100); info.SetRotation(90); info.SetPosition(pt.x, pt.y); movieClip.SetDisplayInfo(info);
Functions in the ActionScript2 Virtual Machine (AS2 VM) are similar to basic objects. These function objects can be assigned members, which could provide extra introspection information depending on the use case. With the GFx::Movie::CreateFunction method, developers are able to create function objects that wrap C++ callback objects. The function object returned by CreateFunction can then be assigned as a member of any object in the VM. When this AS function is invoked, the C++ callback is invoked in turn. This ability allows developers to register direct callbacks from the VM to their own callback handlers without the additional overhead of delegation (via fscommand or ExternalInterface).
The following example creates a custom callback and assigns it to a member of an AS object:
Value obj; pmovie->GetVariable(&obj, "_root.obj"); class MyFunc : public FunctionHandler { public: virtual void Call(const Params& params) { // Callback logic/handling } }; Ptr<MyFunc> customFunc = *SF_HEAP_NEW(Memory::GetGlobalHeap()) MyFunc(); Value func; pmovie->CreateFunction(&func, customFunc); obj.SetMember("func", func);
This method can be invoked in ActionScript (in the _root timeline):
The Params structure passed to the Call() method will contain the following:
Function objects can be used to override existing functions in the VM as well as to inject custom behavior at the beginning or end of an existing function body. Function objects can also be register static methods or instance methods with a class definition. This can be extended to define a custom class. The following example creates a sample class that can be instantiated in the VM:
Value networkProto, networkCtorFn, networkConnectFn; class NetworkClass : public FunctionHandler { public: enum Method { METHOD_Ctor, METHOD_Connet, }; virtual ~NetworkClass() {} virtual void Call(const Params& params) { int method = int(params.pUserData); switch (method) { case METHOD_Ctor: // Custom logic break; case METHOD_Connet: // Custom logic break; } } }; Ptr<NetworkClass> networkClassDef = *SF_HEAP_NEW(Memory::GetGlobalHeap()) NetworkClass(); // Create the constructor function pmovie->CreateFunction(&networkCtorFn, networkClassDef, (void*)NetworkClass::METHOD_Ctor); // Create the prototype object pmovie->CreateObject(&networkProto); // Set the prototype on the constructor function networkCtorFn.SetMember("prototype", networkProto); // Create the prototype method for 'connect' pmovie->CreateFunction(&networkConnectFn, networkClassDef, (void*)NetworkClass::METHOD_Connet); networkProto.SetMember("connect", networkConnectFn); // Register the constructor function with _global pmovie->SetVariable("_global.Network", networkCtorFn);
The class can now be instantiated in the VM:
var netObj:Object = new Network(param1, param2);
Injecting behavior is intended for developers with expert knowledge of the VM as well as lifetime management of VM objects. The following example demonstrates behavior injection:
Value origFuncReal; obj.GetMember("funcReal", &origFuncReal); class FuncRealIntercept : public FunctionHandler { Value OrigFunc; public: FuncRealIntercept(Value origFunc) : OrigFunc(origFunc) {} virtual ~FuncRealIntercept() {} virtual void Call(const Params& params) { // Intercept logic (beginning) OrigFunc.Invoke("call", params.pRetVal, params.pArgsWithThisRef, params.ArgCount + 1); // Intercept logic (end) } }; Value funcRealIntercept; Ptr<FuncRealIntercept> funcRealDef = *SF_HEAP_NEW(Memory::GetGlobalHeap()) FuncRealIntercept(origFuncReal); pmovie->CreateFunction(&funcRealIntercept, funcRealDef); obj.SetMember("funcReal", funcRealIntercept);
NOTE: The lifetime of the GFx::Value held inside the custom function context object must be maintained by the developer because it holds a reference to a VM object. The developer is required to clear the reference before the .swf (GFx::Movie) dies. This requirement is consistent with the lifetime management requirement associated with all GFx::Values that hold references to complex objects from the VM.
Here are the public member functions in the Direct Access API. Please see the online documentation on GFx::Value for the latest information.