Ver Fonte

Initial work for serializing Node / Component handles from a script object automatically.

Lasse Öörni há 12 anos atrás
pai
commit
dda39f2b7d

+ 2 - 5
Bin/Data/Scripts/18_CharacterDemo.as

@@ -240,9 +240,6 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
             characterNode = scene_.GetChild("Jack", true);
             characterNode = scene_.GetChild("Jack", true);
             if (characterNode is null)
             if (characterNode is null)
                 return;
                 return;
-            character = cast<Character>(characterNode.scriptObject);
-            if (character is null)
-                return;
         }
         }
     }
     }
     else
     else
@@ -297,8 +294,8 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 //
 //
 // Those public member variables that can be expressed with a Variant and do not begin with an underscore are automatically
 // Those public member variables that can be expressed with a Variant and do not begin with an underscore are automatically
 // loaded / saved as attributes of the ScriptInstance component. We also have variables which can not be automatically saved
 // loaded / saved as attributes of the ScriptInstance component. We also have variables which can not be automatically saved
-// (controls yaw and pitch) so we write manual binary format load / save functions for them. These functions will be called by
-// ScriptInstance when the script object is being loaded or saved.
+// (yaw and pitch inside the Controls object) so we write manual binary format load / save methods for them. These functions
+// will be called by ScriptInstance when the script object is being loaded or saved.
 class Character : ScriptObject
 class Character : ScriptObject
 {
 {
     // Character controls.
     // Character controls.

+ 45 - 10
Bin/Data/Scripts/19_VehicleDemo.as

@@ -116,7 +116,7 @@ void CreateVehicle()
 {
 {
     vehicleNode = scene_.CreateChild("Vehicle");
     vehicleNode = scene_.CreateChild("Vehicle");
     vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
     vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
-    
+
     // Create the vehicle logic script object
     // Create the vehicle logic script object
     Vehicle@ vehicle = cast<Vehicle>(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
     Vehicle@ vehicle = cast<Vehicle>(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
     // Create the rendering and physics components
     // Create the rendering and physics components
@@ -127,8 +127,11 @@ void CreateInstructions()
 {
 {
     // Construct new Text object, set string to display and font to use
     // Construct new Text object, set string to display and font to use
     Text@ instructionText = ui.root.CreateChild("Text");
     Text@ instructionText = ui.root.CreateChild("Text");
-    instructionText.text = "Use WASD keys to drive, mouse to rotate camera";
+    instructionText.text = "Use WASD keys to drive, mouse to rotate camera\n"
+        "F5 to save scene, F7 to load";
     instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
     instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+    // The text has multiple rows. Center them in relation to each other
+    instructionText.textAlignment = HA_CENTER;
 
 
     // Position the text relative to the screen center
     // Position the text relative to the screen center
     instructionText.horizontalAlignment = HA_CENTER;
     instructionText.horizontalAlignment = HA_CENTER;
@@ -140,7 +143,7 @@ void SubscribeToEvents()
 {
 {
     // Subscribe to Update event for setting the vehicle controls before physics simulation
     // Subscribe to Update event for setting the vehicle controls before physics simulation
     SubscribeToEvent("Update", "HandleUpdate");
     SubscribeToEvent("Update", "HandleUpdate");
-    
+
     // Subscribe to PostUpdate event for updating the camera position after physics simulation
     // Subscribe to PostUpdate event for updating the camera position after physics simulation
     SubscribeToEvent("PostUpdate", "HandlePostUpdate");
     SubscribeToEvent("PostUpdate", "HandlePostUpdate");
 }
 }
@@ -149,7 +152,7 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     if (vehicleNode is null)
     if (vehicleNode is null)
         return;
         return;
-    
+
     Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
     Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
     if (vehicle is null)
     if (vehicle is null)
         return;
         return;
@@ -161,12 +164,27 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         vehicle.controls.Set(CTRL_BACK, input.keyDown['S']);
         vehicle.controls.Set(CTRL_BACK, input.keyDown['S']);
         vehicle.controls.Set(CTRL_LEFT, input.keyDown['A']);
         vehicle.controls.Set(CTRL_LEFT, input.keyDown['A']);
         vehicle.controls.Set(CTRL_RIGHT, input.keyDown['D']);
         vehicle.controls.Set(CTRL_RIGHT, input.keyDown['D']);
-    
+
         // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
         // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
         vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
         vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
         vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
         vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
         // Limit pitch
         // Limit pitch
         vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
         vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
+
+        // Check for loading / saving the scene
+        if (input.keyPress[KEY_F5])
+        {
+            File saveFile(fileSystem.programDir + "Data/Scenes/VehicleDemo.xml", FILE_WRITE);
+            scene_.SaveXML(saveFile);
+        }
+        if (input.keyPress[KEY_F7])
+        {
+            File loadFile(fileSystem.programDir + "Data/Scenes/VehicleDemo.xml", FILE_READ);
+            scene_.LoadXML(loadFile);
+            // After loading we have to reacquire the vehicle scene node, as it has been recreated
+            // Simply find by name as there's only one of them
+            vehicleNode = scene_.GetChild("Vehicle", true);
+        }
     }
     }
     else
     else
         vehicle.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT, false);
         vehicle.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT, false);
@@ -176,7 +194,7 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     if (vehicleNode is null)
     if (vehicleNode is null)
         return;
         return;
-    
+
     Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
     Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
     if (vehicle is null)
     if (vehicle is null)
         return;
         return;
@@ -185,7 +203,7 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
     Quaternion dir(vehicleNode.rotation.yaw, Vector3(0.0f, 1.0f, 0.0f));
     Quaternion dir(vehicleNode.rotation.yaw, Vector3(0.0f, 1.0f, 0.0f));
     dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
     dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
     dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0f, 0.0f, 0.0f));
     dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0f, 0.0f, 0.0f));
-    
+
     Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
     Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
     Vector3 cameraStartPos = vehicleNode.position;
     Vector3 cameraStartPos = vehicleNode.position;
 
 
@@ -202,6 +220,11 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 }
 }
 
 
 // Vehicle script object class
 // Vehicle script object class
+//
+// When saving, the node and component handles are automatically converted into nodeID or componentID attributes
+// and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
+// The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
+
 class Vehicle : ScriptObject
 class Vehicle : ScriptObject
 {
 {
     Node@ frontLeft;
     Node@ frontLeft;
@@ -221,6 +244,18 @@ class Vehicle : ScriptObject
     // Vehicle controls.
     // Vehicle controls.
     Controls controls;
     Controls controls;
 
 
+    void Load(Deserializer& deserializer)
+    {
+        controls.yaw = deserializer.ReadFloat();
+        controls.pitch = deserializer.ReadFloat();
+    }
+
+    void Save(Serializer& serializer)
+    {
+        serializer.WriteFloat(controls.yaw);
+        serializer.WriteFloat(controls.pitch);
+    }
+
     void Init()
     void Init()
     {
     {
         StaticModel@ hullObject = node.CreateComponent("StaticModel");
         StaticModel@ hullObject = node.CreateComponent("StaticModel");
@@ -236,7 +271,7 @@ class Vehicle : ScriptObject
         hullBody.linearDamping = 0.2f; // Some air resistance
         hullBody.linearDamping = 0.2f; // Some air resistance
         hullBody.angularDamping = 0.5f;
         hullBody.angularDamping = 0.5f;
         hullBody.collisionLayer = 1;
         hullBody.collisionLayer = 1;
-    
+
         frontLeft = InitWheel("FrontLeft", Vector3(-0.6f, -0.4f, 0.3f));
         frontLeft = InitWheel("FrontLeft", Vector3(-0.6f, -0.4f, 0.3f));
         frontRight = InitWheel("FrontRight", Vector3(0.6f, -0.4f, 0.3f));
         frontRight = InitWheel("FrontRight", Vector3(0.6f, -0.4f, 0.3f));
         rearLeft = InitWheel("RearLeft", Vector3(-0.6f, -0.4f, -0.3f));
         rearLeft = InitWheel("RearLeft", Vector3(-0.6f, -0.4f, -0.3f));
@@ -282,7 +317,7 @@ class Vehicle : ScriptObject
         wheelConstraint.lowLimit = Vector2(-180.0f, 0.0f); // Let the wheel rotate freely around the axis
         wheelConstraint.lowLimit = Vector2(-180.0f, 0.0f); // Let the wheel rotate freely around the axis
         wheelConstraint.highLimit = Vector2(180.0f, 0.0f);
         wheelConstraint.highLimit = Vector2(180.0f, 0.0f);
         wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull
         wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull
-    
+
         return wheelNode;
         return wheelNode;
     }
     }
 
 
@@ -319,7 +354,7 @@ class Vehicle : ScriptObject
         {
         {
             // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
             // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
             Vector3 torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0f, 0.0f);
             Vector3 torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0f, 0.0f);
-            
+
             frontLeftBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
             frontLeftBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
             frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
             frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
             rearLeftBody.ApplyTorque(node.rotation * torqueVec);
             rearLeftBody.ApplyTorque(node.rotation * torqueVec);

+ 2 - 1
Docs/Reference.dox

@@ -409,7 +409,8 @@ Note that these are not actual Node member functions on the C++ side, as the %Sc
 
 
 \section Scripting_ObjectSerialization Script object serialization
 \section Scripting_ObjectSerialization Script object serialization
 
 
-After instantiation, the script object's public member variables that can be converted into Variant, and that don't begin with an underscore are automatically available as attributes of the ScriptInstance, and will be serialized. Note: this means that a ScriptInstance's attribute list changes dynamically depending on the class that has been instantiated.
+After instantiation, the script object's public member variables that can be converted into Variant, and that don't begin with an underscore are automatically available as attributes of the ScriptInstance, and will be serialized.
+Node and Component handles are also converted into nodeID and componentID attributes automatically. Note: this automatic attribute mechanism means that a ScriptInstance's attribute list changes dynamically depending on the class that has been instantiated.
 
 
 If the script object contains more complex data structures, you can also serialize and deserialize into a binary buffer manually by implementing the Load() and Save() methods.
 If the script object contains more complex data structures, you can also serialize and deserialize into a binary buffer manually by implementing the Load() and Save() methods.
 
 

+ 131 - 18
Source/Engine/Script/ScriptInstance.cpp

@@ -57,6 +57,8 @@ static const char* methodDeclarations[] = {
     "void ApplyAttributes()"
     "void ApplyAttributes()"
 };
 };
 
 
+extern const char* UI_CATEGORY;
+
 ScriptInstance::ScriptInstance(Context* context) :
 ScriptInstance::ScriptInstance(Context* context) :
     Component(context),
     Component(context),
     script_(GetSubsystem<Script>()),
     script_(GetSubsystem<Script>()),
@@ -91,10 +93,68 @@ void ScriptInstance::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_BUFFER, "Script Network Data", GetScriptNetworkDataAttr, SetScriptNetworkDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_NET | AM_NOEDIT);
     ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_BUFFER, "Script Network Data", GetScriptNetworkDataAttr, SetScriptNetworkDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_NET | AM_NOEDIT);
 }
 }
 
 
+void ScriptInstance::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
+{
+    if (attr.mode_ & (AM_NODEID | AM_COMPONENTID))
+    {
+        // The component / node to which the ID refers to may not be in scene yet. Delay searching for it to ApplyAttributes
+        idAttributes_[const_cast<AttributeInfo*>(&attr)] = src.GetUInt();
+    }
+    else
+        Serializable::OnSetAttribute(attr, src);
+}
+
+void ScriptInstance::OnGetAttribute(const AttributeInfo& attr, Variant& dest) const
+{
+    // Get ID's of node / component handle attributes
+    if (attr.mode_ & AM_NODEID)
+    {
+        Node* node = *(reinterpret_cast<Node**>(attr.ptr_));
+        unsigned nodeID = node ? node->GetID() : 0;
+        dest = Variant(nodeID);
+    }
+    else if (attr.mode_ & AM_COMPONENTID)
+    {
+        Component* component = *(reinterpret_cast<Component**>(attr.ptr_));
+        unsigned componentID = component ? component->GetID() : 0;
+        dest = Variant(componentID);
+    }
+    else
+        Serializable::OnGetAttribute(attr, dest);
+}
+
 void ScriptInstance::ApplyAttributes()
 void ScriptInstance::ApplyAttributes()
 {
 {
+    // Apply node / component ID's now
+    for (HashMap<AttributeInfo*, unsigned>::Iterator i = idAttributes_.Begin(); i != idAttributes_.End(); ++i)
+    {
+        AttributeInfo& attr = *i->first_;
+        if (attr.mode_ & AM_NODEID)
+        {
+            Node*& nodePtr = *(reinterpret_cast<Node**>(attr.ptr_));
+            // Decrease reference count of the old object, then increment the new
+            if (nodePtr)
+                nodePtr->ReleaseRef();
+            nodePtr = GetScene()->GetNode(i->second_);
+            if (nodePtr)
+                nodePtr->AddRef();
+        }
+        else if (attr.mode_ & AM_COMPONENTID)
+        {
+            Component*& componentPtr = *(reinterpret_cast<Component**>(attr.ptr_));
+            if (componentPtr)
+                componentPtr->ReleaseRef();
+            componentPtr = GetScene()->GetComponent(i->second_);
+            if (componentPtr)
+                componentPtr->AddRef();
+        }
+    }
+    
+    idAttributes_.Clear();
+    
     if (scriptObject_ && methods_[METHOD_APPLYATTRIBUTES])
     if (scriptObject_ && methods_[METHOD_APPLYATTRIBUTES])
         scriptFile_->Execute(scriptObject_, methods_[METHOD_APPLYATTRIBUTES]);
         scriptFile_->Execute(scriptObject_, methods_[METHOD_APPLYATTRIBUTES]);
+
 }
 }
 
 
 void ScriptInstance::OnSetEnabled()
 void ScriptInstance::OnSetEnabled()
@@ -428,14 +488,15 @@ void ScriptInstance::GetScriptMethods()
 
 
 void ScriptInstance::GetScriptAttributes()
 void ScriptInstance::GetScriptAttributes()
 {
 {
+    asIScriptEngine* engine = GetSubsystem<Script>()->GetScriptEngine();
     attributeInfos_ = *context_->GetAttributes(GetTypeStatic());
     attributeInfos_ = *context_->GetAttributes(GetTypeStatic());
-    
+
     unsigned numProperties = scriptObject_->GetPropertyCount();
     unsigned numProperties = scriptObject_->GetPropertyCount();
     for (unsigned i = 0; i < numProperties; ++i)
     for (unsigned i = 0; i < numProperties; ++i)
     {
     {
         const char* name;
         const char* name;
         int typeId;
         int typeId;
-        bool isPrivate;
+        bool isPrivate, isHandle;
         
         
         scriptObject_->GetObjectType()->GetProperty(i, &name, &typeId, &isPrivate);
         scriptObject_->GetObjectType()->GetProperty(i, &name, &typeId, &isPrivate);
         
         
@@ -443,28 +504,80 @@ void ScriptInstance::GetScriptAttributes()
         if (isPrivate || name[0] == '_')
         if (isPrivate || name[0] == '_')
             continue;
             continue;
         
         
+        String typeName = engine->GetTypeDeclaration(typeId);
+        isHandle = typeName.EndsWith("@");
+        if (isHandle)
+            typeName = typeName.Substring(0, typeName.Length() - 1);
+        
         AttributeInfo info;
         AttributeInfo info;
         info.name_ = name;
         info.name_ = name;
         info.ptr_ = scriptObject_->GetAddressOfProperty(i);
         info.ptr_ = scriptObject_->GetAddressOfProperty(i);
         
         
-        switch (typeId)
+        if (!isHandle)
         {
         {
-        case asTYPEID_BOOL:
-            info.type_ = VAR_BOOL;
-            break;
-            
-        case asTYPEID_INT32:
-        case asTYPEID_UINT32:
-            info.type_ = VAR_INT;
-            break;
-            
-        case asTYPEID_FLOAT:
-            info.type_ = VAR_FLOAT;
-            break;
+            switch (typeId)
+            {
+            case asTYPEID_BOOL:
+                info.type_ = VAR_BOOL;
+                break;
+                
+            case asTYPEID_INT32:
+            case asTYPEID_UINT32:
+                info.type_ = VAR_INT;
+                break;
+                
+            case asTYPEID_FLOAT:
+                info.type_ = VAR_FLOAT;
+                break;
+                
+            default:
+                info.type_ = Variant::GetTypeFromName(typeName);
+                break;
+            }
+        }
+        else
+        {
+            // For a handle type, check if it's an Object subclass with a registered factory
+            ShortStringHash typeHash(typeName);
             
             
-        default:
-            info.type_ = Variant::GetTypeFromName(GetSubsystem<Script>()->GetScriptEngine()->GetTypeDeclaration(typeId));
-            break;
+            if (context_->GetObjectFactories().Find(typeHash) != context_->GetObjectFactories().End())
+            {
+                // There are three possibilities: the handle is for a Node, a Component or UIElement subclass
+                // We want to identify nodes and components so that we can store their ID's for serialization
+                const HashMap<String, Vector<ShortStringHash> >& categories = context_->GetObjectCategories();
+                if (categories.Contains(UI_CATEGORY))
+                {
+                    const Vector<ShortStringHash>& uiCategory = categories.Find(UI_CATEGORY)->second_;
+                    if (uiCategory.Contains(typeHash))
+                        continue; // Is handle to a UIElement; can not handle those
+                }
+                
+                if (typeHash == Node::GetTypeStatic() || typeHash == Scene::GetTypeStatic())
+                {
+                    info.mode_ |= AM_NODEID;
+                    info.type_ = VAR_INT;
+                }
+                else
+                {
+                    bool foundComponent = false;
+                    // If is found in one of the component categories, must be a component
+                    for (HashMap<String, Vector<ShortStringHash> >::ConstIterator i = categories.Begin(); i != categories.End();
+                        ++i)
+                    {
+                        if (i->second_.Contains(typeHash))
+                        {
+                            foundComponent = true;
+                            break;
+                        }
+                    }
+                    
+                    if (foundComponent)
+                    {
+                        info.mode_ |= AM_COMPONENTID;
+                        info.type_ = VAR_INT;
+                    }
+                }
+            }
         }
         }
         
         
         if (info.type_ != VAR_NONE)
         if (info.type_ != VAR_NONE)

+ 6 - 0
Source/Engine/Script/ScriptInstance.h

@@ -80,6 +80,10 @@ public:
     /// Register object factory.
     /// Register object factory.
     static void RegisterObject(Context* context);
     static void RegisterObject(Context* context);
     
     
+    /// Handle attribute write access.
+    virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
+    /// Handle attribute read access.
+    virtual void OnGetAttribute(const AttributeInfo& attr, Variant& dest) const;
     /// Return attribute descriptions, or null if none defined.
     /// Return attribute descriptions, or null if none defined.
     virtual const Vector<AttributeInfo>* GetAttributes() const { return &attributeInfos_; }
     virtual const Vector<AttributeInfo>* GetAttributes() const { return &attributeInfos_; }
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
@@ -190,6 +194,8 @@ private:
     Vector<DelayedMethodCall> delayedMethodCalls_;
     Vector<DelayedMethodCall> delayedMethodCalls_;
     /// Attributes, including script object variables.
     /// Attributes, including script object variables.
     Vector<AttributeInfo> attributeInfos_;
     Vector<AttributeInfo> attributeInfos_;
+    /// Storage for unapplied node and component ID attributes
+    HashMap<AttributeInfo*, unsigned> idAttributes_;
     /// Subscribed to scene update events flag.
     /// Subscribed to scene update events flag.
     bool subscribed_;
     bool subscribed_;
     /// Subscribed to scene post and fixed update events flag.
     /// Subscribed to scene post and fixed update events flag.