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

This commit is contained in:
Lasse Öörni 2013-09-15 19:08:05 +00:00
parent 1034f8856f
commit dda39f2b7d
5 changed files with 186 additions and 34 deletions

View File

@ -240,9 +240,6 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
characterNode = scene_.GetChild("Jack", true);
if (characterNode is null)
return;
character = cast<Character>(characterNode.scriptObject);
if (character is null)
return;
}
}
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
// 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
{
// Character controls.

View File

@ -116,7 +116,7 @@ void CreateVehicle()
{
vehicleNode = scene_.CreateChild("Vehicle");
vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
// Create the vehicle logic script object
Vehicle@ vehicle = cast<Vehicle>(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
// Create the rendering and physics components
@ -127,8 +127,11 @@ void CreateInstructions()
{
// Construct new Text object, set string to display and font to use
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);
// The text has multiple rows. Center them in relation to each other
instructionText.textAlignment = HA_CENTER;
// Position the text relative to the screen center
instructionText.horizontalAlignment = HA_CENTER;
@ -140,7 +143,7 @@ void SubscribeToEvents()
{
// Subscribe to Update event for setting the vehicle controls before physics simulation
SubscribeToEvent("Update", "HandleUpdate");
// Subscribe to PostUpdate event for updating the camera position after physics simulation
SubscribeToEvent("PostUpdate", "HandlePostUpdate");
}
@ -149,7 +152,7 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
{
if (vehicleNode is null)
return;
Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
if (vehicle is null)
return;
@ -161,12 +164,27 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
vehicle.controls.Set(CTRL_BACK, input.keyDown['S']);
vehicle.controls.Set(CTRL_LEFT, input.keyDown['A']);
vehicle.controls.Set(CTRL_RIGHT, input.keyDown['D']);
// 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.pitch += input.mouseMoveY * YAW_SENSITIVITY;
// Limit pitch
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
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)
return;
Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
if (vehicle is null)
return;
@ -185,7 +203,7 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
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.pitch, Vector3(1.0f, 0.0f, 0.0f));
Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
Vector3 cameraStartPos = vehicleNode.position;
@ -202,6 +220,11 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
}
// 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
{
Node@ frontLeft;
@ -221,6 +244,18 @@ class Vehicle : ScriptObject
// Vehicle 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()
{
StaticModel@ hullObject = node.CreateComponent("StaticModel");
@ -236,7 +271,7 @@ class Vehicle : ScriptObject
hullBody.linearDamping = 0.2f; // Some air resistance
hullBody.angularDamping = 0.5f;
hullBody.collisionLayer = 1;
frontLeft = InitWheel("FrontLeft", 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));
@ -282,7 +317,7 @@ class Vehicle : ScriptObject
wheelConstraint.lowLimit = Vector2(-180.0f, 0.0f); // Let the wheel rotate freely around the axis
wheelConstraint.highLimit = Vector2(180.0f, 0.0f);
wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull
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
Vector3 torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0f, 0.0f);
frontLeftBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
rearLeftBody.ApplyTorque(node.rotation * torqueVec);

View File

@ -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
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.

View File

@ -57,6 +57,8 @@ static const char* methodDeclarations[] = {
"void ApplyAttributes()"
};
extern const char* UI_CATEGORY;
ScriptInstance::ScriptInstance(Context* context) :
Component(context),
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);
}
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()
{
// 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])
scriptFile_->Execute(scriptObject_, methods_[METHOD_APPLYATTRIBUTES]);
}
void ScriptInstance::OnSetEnabled()
@ -428,14 +488,15 @@ void ScriptInstance::GetScriptMethods()
void ScriptInstance::GetScriptAttributes()
{
asIScriptEngine* engine = GetSubsystem<Script>()->GetScriptEngine();
attributeInfos_ = *context_->GetAttributes(GetTypeStatic());
unsigned numProperties = scriptObject_->GetPropertyCount();
for (unsigned i = 0; i < numProperties; ++i)
{
const char* name;
int typeId;
bool isPrivate;
bool isPrivate, isHandle;
scriptObject_->GetObjectType()->GetProperty(i, &name, &typeId, &isPrivate);
@ -443,28 +504,80 @@ void ScriptInstance::GetScriptAttributes()
if (isPrivate || name[0] == '_')
continue;
String typeName = engine->GetTypeDeclaration(typeId);
isHandle = typeName.EndsWith("@");
if (isHandle)
typeName = typeName.Substring(0, typeName.Length() - 1);
AttributeInfo info;
info.name_ = name;
info.ptr_ = scriptObject_->GetAddressOfProperty(i);
switch (typeId)
if (!isHandle)
{
case asTYPEID_BOOL:
info.type_ = VAR_BOOL;
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);
case asTYPEID_INT32:
case asTYPEID_UINT32:
info.type_ = VAR_INT;
break;
case asTYPEID_FLOAT:
info.type_ = VAR_FLOAT;
break;
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)

View File

@ -80,6 +80,10 @@ public:
/// Register object factory.
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.
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.
@ -190,6 +194,8 @@ private:
Vector<DelayedMethodCall> delayedMethodCalls_;
/// Attributes, including script object variables.
Vector<AttributeInfo> attributeInfos_;
/// Storage for unapplied node and component ID attributes
HashMap<AttributeInfo*, unsigned> idAttributes_;
/// Subscribed to scene update events flag.
bool subscribed_;
/// Subscribed to scene post and fixed update events flag.