Browse Source

Add the ability to allow pure script objects that are not created through a ScriptInstance to handle events. This works like it currently does using:

    SubscribeToEvent("Foo","FooHandler");

Where if called inside a procedural function the ScriptFile will handle it, if called inside an object if that object was created through a ScriptInstance that ScriptInstance will handle the event, otherwise the ScriptFile associated with the current script module will handle it.
Alex Parlett 12 years ago
parent
commit
18cb81b750

+ 65 - 0
Source/Engine/Script/ScriptEventListener.cpp

@@ -0,0 +1,65 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "ScriptEventListener.h"
+
+#include <angelscript.h>
+
+namespace Urho3D
+{
+
+ScriptEventData::ScriptEventData(asIScriptFunction* function, asIScriptObject* object) :
+    sharedbool_(0),
+    function_(function),
+    object_(object)
+{
+    if (object_)
+    {
+        sharedbool_ = object_->GetEngine()->GetWeakRefFlagOfScriptObject(object_, object_->GetObjectType());
+        if (sharedbool_)
+            sharedbool_->AddRef();
+    }
+}
+
+ScriptEventData::~ScriptEventData()
+{
+    if (sharedbool_)
+        sharedbool_->Release();
+
+    sharedbool_ = 0;
+    function_ = 0;
+    object_ = 0;
+}
+
+bool ScriptEventData::IsObjectAlive() const
+{
+    if (sharedbool_)
+    {
+        // Return inverse as Get returns true when an asIScriptObject is dead.
+        return !sharedbool_->Get();
+    }
+
+    return true;
+}
+
+}

+ 29 - 1
Source/Engine/Script/ScriptEventListener.h

@@ -24,13 +24,17 @@
 
 #include "Object.h"
 
+class asILockableSharedBool;
+class asIScriptFunction;
+class asIScriptObject;
+
 namespace Urho3D
 {
 
 /// Delay-executed function or method call.
 struct DelayedCall
 {
-   /// Period for repeating calls.
+    /// Period for repeating calls.
     float period_;
     /// Delay time remaining until execution.
     float delay_;
@@ -48,10 +52,34 @@ class URHO3D_API ScriptEventListener
 public:
     /// Destruct
     virtual ~ScriptEventListener() {};
+
     /// Add a scripted event handler. Called by script exposed version of SubscribeToEvent().
     virtual void AddEventHandler(StringHash eventType, const String& handlerName) = 0;
     /// Add a scripted event handler for a specific sender. Called by script exposed version of SubscribeToEvent().
     virtual void AddEventHandler(Object* sender, StringHash eventType, const String& handlerName) = 0;
 };
 
+/// Holds the data required to send events to scripts.
+class URHO3D_API ScriptEventData : public RefCounted
+{
+public:
+    /// Constructor, will create the asILockableSharedBool if a ScriptObject is passed in.
+    ScriptEventData(asIScriptFunction* function, asIScriptObject* object = 0);
+    /// Destructor, release the ref it we still hold it.
+    ~ScriptEventData();
+
+    /// Get the asIScriptFunction to call.
+    asIScriptFunction* GetFunction() const { return function_; }
+    /// Get the asIScriptObject to call the method on, can be null.
+    asIScriptObject* GetObject() const { return object_; }
+
+    /// Returns whether a ScriptObject is still alive. Will return true if there is no reference and object.
+    bool IsObjectAlive() const;
+
+private:
+    asILockableSharedBool* sharedbool_;
+    asIScriptFunction* function_;
+    asIScriptObject* object_;
+};
+
 }

+ 103 - 58
Source/Engine/Script/ScriptFile.cpp

@@ -166,52 +166,80 @@ bool ScriptFile::Load(Deserializer& source)
     return true;
 }
 
-void ScriptFile::AddEventHandler(StringHash eventType, const String& handlerName)
-{
-    if (!compiled_)
-        return;
-    
-    String declaration = "void " + handlerName + "(StringHash, VariantMap&)";
-    asIScriptFunction* function = GetFunction(declaration);
-    if (!function)
-    {
-        declaration = "void " + handlerName + "()";
-        function = GetFunction(declaration);
-        if (!function)
-        {
-            LOGERROR("Event handler function " + handlerName + " not found in " + GetName());
-            return;
-        }
-    }
-    
-    SubscribeToEvent(eventType, HANDLER_USERDATA(ScriptFile, HandleScriptEvent, (void*)function));
-}
-
-void ScriptFile::AddEventHandler(Object* sender, StringHash eventType, const String& handlerName)
-{
-    if (!compiled_)
-        return;
-    
-    if (!sender)
-    {
-        LOGERROR("Null event sender for event " + String(eventType) + ", handler " + handlerName);
-        return;
-    }
-    
-    String declaration = "void " + handlerName + "(StringHash, VariantMap&)";
-    asIScriptFunction* function = GetFunction(declaration);
-    if (!function)
-    {
-        declaration = "void " + handlerName + "()";
-        function = GetFunction(declaration);
-        if (!function)
-        {
-            LOGERROR("Event handler function " + handlerName + " not found in " + GetName());
-            return;
-        }
-    }
-    
-    SubscribeToEvent(sender, eventType, HANDLER_USERDATA(ScriptFile, HandleScriptEvent, (void*)function));
+void ScriptFile::AddEventHandler(StringHash eventType, const String& handlerName)
+{
+    if (!compiled_)
+        return;
+
+    String declaration = "void " + handlerName + "(StringHash, VariantMap&)";
+    asIScriptFunction* function = 0;
+    asIScriptObject* reciever = static_cast<asIScriptObject*>(asGetActiveContext()->GetThisPointer());
+
+    if (reciever)
+        function = GetMethod(reciever, declaration);
+    else
+        function = GetFunction(declaration);
+
+    if (!function)
+    {
+        declaration = "void " + handlerName + "()";
+
+        if (reciever)
+            function = GetMethod(reciever, declaration);
+        else
+            function = GetFunction(declaration);
+
+        if (!function)
+        {
+            LOGERROR("Event handler function " + handlerName + " not found in " + GetName());
+            return;
+        }
+    }
+
+    SharedPtr<ScriptEventData> data(new ScriptEventData(function, reciever));
+    scriptEventData_.Push(data);
+    SubscribeToEvent(eventType, HANDLER_USERDATA(ScriptFile, HandleScriptEvent, data.Get()));
+}
+
+void ScriptFile::AddEventHandler(Object* sender, StringHash eventType, const String& handlerName)
+{
+    if (!compiled_)
+        return;
+
+    if (!sender)
+    {
+        LOGERROR("Null event sender for event " + String(eventType) + ", handler " + handlerName);
+        return;
+    }
+
+    String declaration = "void " + handlerName + "(StringHash, VariantMap&)";
+    asIScriptFunction* function = 0;
+    asIScriptObject* reciever = static_cast<asIScriptObject*>(asGetActiveContext()->GetThisPointer());
+
+    if (reciever)
+        function = GetMethod(reciever, declaration);
+    else
+        function = GetFunction(declaration);
+
+    if (!function)
+    {
+        declaration = "void " + handlerName + "()";
+
+        if (reciever)
+            function = GetMethod(reciever, declaration);
+        else
+            function = GetFunction(declaration);
+
+        if (!function)
+        {
+            LOGERROR("Event handler function " + handlerName + " not found in " + GetName());
+            return;
+        }
+    }
+
+    SharedPtr<ScriptEventData> data(new ScriptEventData(function, reciever));
+    scriptEventData_.Push(data);
+    SubscribeToEvent(sender, eventType, HANDLER_USERDATA(ScriptFile, HandleScriptEvent, data.Get()));
 }
 
 bool ScriptFile::Execute(const String& declaration, const VariantVector& parameters, bool unprepare)
@@ -651,19 +679,36 @@ void ScriptFile::ReleaseModule()
 
 void ScriptFile::HandleScriptEvent(StringHash eventType, VariantMap& eventData)
 {
-    if (!compiled_)
-        return;
-    
-    asIScriptFunction* function = static_cast<asIScriptFunction*>(GetEventHandler()->GetUserData());
-    
-    VariantVector parameters;
-    if (function->GetParamCount() > 0)
-    {
-        parameters.Push(Variant((void*)&eventType));
-        parameters.Push(Variant((void*)&eventData));
+    if (!compiled_)
+        return;
+
+    ScriptEventData* data = static_cast<ScriptEventData*>(GetEventHandler()->GetUserData());
+
+    asIScriptObject* object = data->GetObject();
+    asIScriptFunction* method = data->GetFunction();
+
+    if (object && !data->IsObjectAlive())
+    {
+        scriptEventData_.Remove(SharedPtr<ScriptEventData>(data));
+        UnsubscribeFromEvent(eventType);
+        return;
+    }
+
+    VariantVector parameters;
+    if (method->GetParamCount() > 0)
+    {
+        parameters.Push(Variant((void*) &eventType));
+        parameters.Push(Variant((void*) &eventData));
+    }
+
+    if (object)
+    {
+        Execute(object, method, parameters);
+    }
+    else
+    {
+        Execute(method, parameters);
     }
-    
-    Execute(function, parameters);
 }
 
 void ScriptFile::HandleUpdate(StringHash eventType, VariantMap& eventData)

+ 3 - 0
Source/Engine/Script/ScriptFile.h

@@ -55,6 +55,7 @@ public:
     
     /// Load resource. Return true if successful.
     virtual bool Load(Deserializer& source);
+
     /// Add an event handler. Called by script exposed version of SubscribeToEvent().
     virtual void AddEventHandler(StringHash eventType, const String& handlerName);
     /// Add an event handler for a specific sender. Called by script exposed version of SubscribeToEvent().
@@ -116,6 +117,8 @@ private:
     HashMap<asIObjectType*, HashMap<String, asIScriptFunction*> > methods_;
     /// Delayed function calls.
     Vector<DelayedCall> delayedCalls_;
+    /// ScriptEventData objects that this ScriptFile is subscribed with.
+    Vector< SharedPtr<ScriptEventData> > scriptEventData_;
 };
 
 /// Get currently executing script file.

+ 3 - 3
Source/Engine/Script/ScriptInstance.cpp

@@ -860,12 +860,12 @@ Scene* GetScriptContextScene()
 
 ScriptEventListener* GetScriptContextEventListener()
 {
-    // If context's this pointer is non-null, try to get the script instance. Else get the script file for procedural
-    // event handling
+    // If the context has an object and that object has user data set, try and get the ScriptInstance, otherwise try and get a ScriptFile.
     asIScriptContext* context = asGetActiveContext();
     if (context)
     {
-        if (context->GetThisPointer())
+        asIScriptObject* object = static_cast<asIScriptObject*>(context->GetThisPointer());
+        if (object && object->GetUserData())
             return GetScriptContextInstance();
         else
             return GetScriptContextFile();

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

@@ -76,6 +76,7 @@ public:
     virtual void ApplyAttributes();
     /// Handle enabled/disabled state change.
     virtual void OnSetEnabled();
+
     /// Add an event handler. Called by script exposed version of SubscribeToEvent().
     virtual void AddEventHandler(StringHash eventType, const String& handlerName);
     /// Add an event handler for a specific sender. Called by script exposed version of SubscribeToEvent().