[dss-commits] r8766 - in dss/trunk: core core/scripting tests

dss-commits at forum.digitalstrom.org dss-commits at forum.digitalstrom.org
Wed Sep 16 16:29:05 CEST 2009


Author: pstaehlin
Date: 2009-09-16 16:29:05 +0200 (Wed, 16 Sep 2009)
New Revision: 8766

Modified:
   dss/trunk/core/eventinterpreterplugins.cpp
   dss/trunk/core/jshandler.cpp
   dss/trunk/core/jshandler.h
   dss/trunk/core/propertysystem.cpp
   dss/trunk/core/propertysystem.h
   dss/trunk/core/scripting/modeljs.cpp
   dss/trunk/core/scripting/modeljs.h
   dss/trunk/tests/modeljstests.cpp
   dss/trunk/tests/propertysystemtests.cpp
Log:
Support for setListener and removeListener in javascript

Closes #129, #128


Modified: dss/trunk/core/eventinterpreterplugins.cpp
===================================================================
--- dss/trunk/core/eventinterpreterplugins.cpp	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/eventinterpreterplugins.cpp	2009-09-16 14:29:05 UTC (rev 8766)
@@ -80,6 +80,8 @@
           m_Environment.addExtension(ext);
           ext = new EventScriptExtension(DSS::getInstance()->getEventQueue(), getEventInterpreter());
           m_Environment.addExtension(ext);
+          ext = new PropertyScriptExtension(DSS::getInstance()->getPropertySystem());
+          m_Environment.addExtension(ext);
         }
 
         try {

Modified: dss/trunk/core/jshandler.cpp
===================================================================
--- dss/trunk/core/jshandler.cpp	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/jshandler.cpp	2009-09-16 14:29:05 UTC (rev 8766)
@@ -514,8 +514,8 @@
   } // getClassName
 
   template<>
-  jsval ScriptObject::callFunctionByName<jsval>(const std::string& _functionName,
-                                                ScriptFunctionParameterList& _parameter) {
+  jsval ScriptObject::callFunctionByName(const std::string& _functionName,
+                                         ScriptFunctionParameterList& _parameter) {
     int paramc = _parameter.size();
     jsval* paramv = (jsval*)malloc(sizeof(jsval) * paramc);
     for(int iParam = 0; iParam < paramc; iParam++) {
@@ -562,4 +562,53 @@
     return m_Context.convertTo<std::string>(callFunctionByName<jsval>(_functionName, _parameter));
   } // callFunctionByName<std::string>
 
+  template<>
+  jsval ScriptObject::callFunctionByReference(jsval _function,
+                                              ScriptFunctionParameterList& _parameter) {
+    int paramc = _parameter.size();
+    jsval* paramv = (jsval*)malloc(sizeof(jsval) * paramc);
+    for(int iParam = 0; iParam < paramc; iParam++) {
+      paramv[iParam] = _parameter.get(iParam);
+    }
+    jsval rval;
+    JSBool ok = JS_CallFunctionValue(m_Context.getJSContext(), m_pObject, _function, paramc, paramv, &rval);
+    free(paramv);
+    if(ok) {
+      return rval;
+    } else {
+      m_Context.raisePendingExceptions();
+      throw ScriptException("Error running function");
+    }
+  } // callFunctionByReference<jsval>
+
+  template<>
+  void ScriptObject::callFunctionByReference(jsval _function,
+                                             ScriptFunctionParameterList& _parameter) {
+    callFunctionByReference<jsval>(_function, _parameter);
+  } // callFunctionByReference<void>
+
+  template<>
+  int ScriptObject::callFunctionByReference(jsval _function,
+                                            ScriptFunctionParameterList& _parameter) {
+    return m_Context.convertTo<int>(callFunctionByReference<jsval>(_function, _parameter));
+  } // callFunctionByReference<int>
+
+  template<>
+  double ScriptObject::callFunctionByReference(jsval _function,
+                                               ScriptFunctionParameterList& _parameter) {
+    return m_Context.convertTo<double>(callFunctionByReference<jsval>(_function, _parameter));
+  } // callFunctionByReference<double>
+
+  template<>
+  bool ScriptObject::callFunctionByReference(jsval _function,
+                                             ScriptFunctionParameterList& _parameter) {
+    return m_Context.convertTo<bool>(callFunctionByReference<jsval>(_function, _parameter));
+  } // callFunctionByReference<bool>
+
+  template<>
+  std::string ScriptObject::callFunctionByReference(jsval _function,
+                                                    ScriptFunctionParameterList& _parameter) {
+    return m_Context.convertTo<std::string>(callFunctionByReference<jsval>(_function, _parameter));
+  } // callFunctionByReference<std::string>
+
 } // namespace dss

Modified: dss/trunk/core/jshandler.h
===================================================================
--- dss/trunk/core/jshandler.h	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/jshandler.h	2009-09-16 14:29:05 UTC (rev 8766)
@@ -215,8 +215,11 @@
 
     template<class t>
     t callFunctionByName(const std::string& _functionName, ScriptFunctionParameterList& _parameter);
-  }; // ScriptObject
 
+    template<class t>
+    t callFunctionByReference(jsval _function, ScriptFunctionParameterList& _parameter);
+}; // ScriptObject
+
 } // namespace dss
 
 #endif

Modified: dss/trunk/core/propertysystem.cpp
===================================================================
--- dss/trunk/core/propertysystem.cpp	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/propertysystem.cpp	2009-09-16 14:29:05 UTC (rev 8766)
@@ -846,11 +846,19 @@
   } // childRemoved
 
   void PropertyNode::notifyListeners(void (PropertyListener::*_callback)(PropertyNodePtr)) {
-    std::vector<PropertyListener*>::iterator it;
     bool notified = false;
-    for(it = m_Listeners.begin(); it != m_Listeners.end(); ++it) {
-      ((*it)->*_callback)(shared_from_this());
-      notified = true;
+    if(!m_Listeners.empty()) {
+      // as the list might get modified in the listener code we need to iterate
+      // over a copy of our listeners
+      std::vector<PropertyListener*> copy = m_Listeners;
+      std::vector<PropertyListener*>::iterator it;
+      foreach(PropertyListener* pListener, copy) {
+        // check if the original list still contains the listener
+        if(contains(m_Listeners, pListener)) {
+          (pListener->*_callback)(shared_from_this());
+          notified = true;
+        }
+      }
     }
     if(!notified) {
       if(m_ParentNode != NULL) {
@@ -863,11 +871,19 @@
   } // notifyListeners
 
   void PropertyNode::notifyListeners(void (PropertyListener::*_callback)(PropertyNodePtr,PropertyNodePtr), PropertyNodePtr _node) {
-    std::vector<PropertyListener*>::iterator it;
     bool notified = false;
-    for(it = m_Listeners.begin(); it != m_Listeners.end(); ++it) {
-      ((*it)->*_callback)(shared_from_this(), _node);
-      notified = true;
+    if(!m_Listeners.empty()) {
+      // as the list might get modified in the listener code we need to iterate
+      // over a copy of our listeners
+      std::vector<PropertyListener*> copy = m_Listeners;
+      std::vector<PropertyListener*>::iterator it;
+      foreach(PropertyListener* pListener, copy) {
+        // check if the original list still contains the listener
+        if(contains(m_Listeners, pListener)) {
+          (pListener->*_callback)(shared_from_this(), _node);
+          notified = true;
+        }
+      }
     }
     if(!notified) {
       if(m_ParentNode != NULL) {
@@ -883,12 +899,16 @@
   //=============================================== PropertyListener
 
   PropertyListener::~PropertyListener() {
+    unsubscribe();
+  } // dtor
+
+  void PropertyListener::unsubscribe() {
     // while this does look like an infinite loop it isn't
     // as the property will call unregisterProperty in removeListener
     while(!m_Properties.empty()) {
       m_Properties.front()->removeListener(this);
     }
-  } // dtor
+  } // unsubscribe
 
   void PropertyListener::propertyChanged(PropertyNodePtr _changedNode) {
   } // propertyChanged

Modified: dss/trunk/core/propertysystem.h
===================================================================
--- dss/trunk/core/propertysystem.h	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/propertysystem.h	2009-09-16 14:29:05 UTC (rev 8766)
@@ -315,6 +315,8 @@
     /** Remove a property from the notifiers */
     void unregisterProperty(PropertyNode* _node);
   public:
+    bool isListening() { return !m_Properties.empty(); }
+    void unsubscribe();
     virtual ~PropertyListener();
   }; // PropertyListener
 

Modified: dss/trunk/core/scripting/modeljs.cpp
===================================================================
--- dss/trunk/core/scripting/modeljs.cpp	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/scripting/modeljs.cpp	2009-09-16 14:29:05 UTC (rev 8766)
@@ -892,7 +892,8 @@
 
   PropertyScriptExtension::PropertyScriptExtension(PropertySystem& _propertySystem) 
   : ScriptExtension(PropertyScriptExtensionName),
-    m_PropertySystem(_propertySystem)
+    m_PropertySystem(_propertySystem),
+    m_NextListenerID(1)
   { } // ctor
     
   JSBool global_prop_setProperty(JSContext* cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
@@ -960,9 +961,53 @@
     return JS_FALSE;
   } // global_prop_getProperty
 
+  JSBool global_prop_setListener(JSContext* cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+    if(argc > 2) {
+      Logger::getInstance()->log("JS: global_prop_setListener: need two arguments: property-path &  callback", lsError);
+    } else {
+      ScriptContext* ctx = static_cast<ScriptContext*>(JS_GetContextPrivate(cx));
+
+      PropertyScriptExtension* ext = dynamic_cast<PropertyScriptExtension*>(ctx->getEnvironment().getExtension(PropertyScriptExtensionName));
+      std::string propName = ctx->convertTo<std::string>(argv[0]);
+
+      PropertyNodePtr node = ext->getPropertySystem().getProperty(propName);
+      if(node == NULL) {
+        *rval = JSVAL_NULL;
+      } else {
+        std::string ident = ext->produceListenerID();
+        PropertyScriptListener* listener =
+            new PropertyScriptListener(ext, ctx, obj, argv[1], ident);
+        ext->addListener(listener);
+        node->addListener(listener);
+        JSString* str = JS_NewStringCopyZ(cx, ident.c_str());
+        *rval = STRING_TO_JSVAL(str);
+      }
+      return JS_TRUE;
+    }
+    return JS_FALSE;
+  } // global_prop_setListener
+
+  JSBool global_prop_removeListener(JSContext* cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
+    if(argc > 2) {
+      Logger::getInstance()->log("JS: global_prop_removeListener: need two arguments: listener-id", lsError);
+    } else {
+      ScriptContext* ctx = static_cast<ScriptContext*>(JS_GetContextPrivate(cx));
+
+      PropertyScriptExtension* ext = dynamic_cast<PropertyScriptExtension*>(ctx->getEnvironment().getExtension(PropertyScriptExtensionName));
+      std::string listenerIdent = ctx->convertTo<std::string>(argv[0]);
+      ext->removeListener(listenerIdent);
+
+      *rval = JSVAL_TRUE;
+      return JS_TRUE;
+    }
+    return JS_FALSE;
+  } // global_prop_setListener
+
   JSFunctionSpec prop_global_methods[] = {
     {"setProperty", global_prop_setProperty, 2, 0, 0},
     {"getProperty", global_prop_getProperty, 1, 0, 0},
+    {"setListener", global_prop_setListener, 2, 0, 0},
+    {"removeListener", global_prop_removeListener, 1, 0, 0},
     {NULL},
   };
 
@@ -974,5 +1019,61 @@
     return NULL;
   } // createJSProperty
 
+  std::string PropertyScriptExtension::produceListenerID() {
+    return "listener_" + intToString(int(this), true) + "_" + intToString(m_NextListenerID++);
+  } // produceListenerID
 
+  void PropertyScriptExtension::addListener(PropertyScriptListener* _pListener) {
+    m_Listeners.push_back(_pListener);
+  } // addListener
+
+  void PropertyScriptExtension::removeListener(const std::string& _identifier) {
+    for(boost::ptr_vector<PropertyScriptListener>::iterator it = m_Listeners.begin(), e = m_Listeners.end();
+        it != e; ++it) {
+      if(it->getIdentifier() == _identifier) {
+        it->unsubscribe();
+        return;
+      }
+    }
+  }
+
+
+  //================================================== PropertyScriptListener
+  
+  PropertyScriptListener::PropertyScriptListener(PropertyScriptExtension* _pExtension,
+                                                 ScriptContext* _pContext, 
+                                                 JSObject* _functionObj, 
+                                                 jsval _function, 
+                                                 const std::string& _identifier)
+  : m_pExtension(_pExtension),
+    m_pContext(_pContext),
+    m_pFunctionObject(_functionObj),
+    m_Function(_function),
+    m_Identifier(_identifier)
+  { } // ctor
+
+  void PropertyScriptListener::createScriptObject() {
+    if(m_pScriptObject == NULL) {
+      m_pScriptObject.reset(new ScriptObject(m_pFunctionObject, *m_pContext));
+    }
+    assert(m_pScriptObject != NULL);
+  } // createScriptObject
+
+  void PropertyScriptListener::propertyChanged(PropertyNodePtr _changedNode) {
+    doOnChange(_changedNode);
+  } // propertyChanged
+  
+  void PropertyScriptListener::propertyRemoved(PropertyNodePtr _parent, PropertyNodePtr _child) {
+  } // propertyRemoved
+  
+  void PropertyScriptListener::propertyAdded(PropertyNodePtr _parent, PropertyNodePtr _child) {
+  } // propertyAdded
+
+  void PropertyScriptListener::doOnChange(PropertyNodePtr _changedNode) {
+    createScriptObject();
+    ScriptFunctionParameterList list(*m_pContext);
+    list.add(_changedNode->getDisplayName());
+    m_pScriptObject->callFunctionByReference<void>(m_Function, list);
+  } // doOnChange
+
 } // namespace

Modified: dss/trunk/core/scripting/modeljs.h
===================================================================
--- dss/trunk/core/scripting/modeljs.h	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/core/scripting/modeljs.h	2009-09-16 14:29:05 UTC (rev 8766)
@@ -24,10 +24,13 @@
 
 #include <bitset>
 
-#include "../jshandler.h"
-#include "../model.h"
-#include "../event.h"
+#include <boost/ptr_container/ptr_vector.hpp>
 
+#include "core/jshandler.h"
+#include "core/model.h"
+#include "core/event.h"
+#include "core/propertysystem.h"
+
 namespace dss {
 
   /** This class extends a ScriptContext to contain the JS-API of the Apartment */
@@ -86,7 +89,29 @@
   
   class PropertySystem;
   class PropertyNode;
+  class PropertyScriptExtension;
   
+  class PropertyScriptListener : public PropertyListener {
+  public:
+    PropertyScriptListener(PropertyScriptExtension* _pExtension, ScriptContext* _pContext, JSObject* _functionObj, jsval _function, const std::string& _identifier);
+
+    virtual void propertyChanged(PropertyNodePtr _changedNode);
+    virtual void propertyRemoved(PropertyNodePtr _parent, PropertyNodePtr _child);
+    virtual void propertyAdded(PropertyNodePtr _parent, PropertyNodePtr _child);
+
+    const std::string& getIdentifier() const { return m_Identifier; }
+  private:
+    void doOnChange(PropertyNodePtr _changedNode);
+    void createScriptObject();
+  private:
+    PropertyScriptExtension* m_pExtension;
+    ScriptContext* m_pContext;
+    JSObject* m_pFunctionObject;
+    jsval m_Function;
+    std::string m_Identifier;
+    boost::scoped_ptr<ScriptObject> m_pScriptObject;
+  }; // PropertyScriptListener
+  
   class PropertyScriptExtension : public ScriptExtension {
   public:
     PropertyScriptExtension(PropertySystem& _propertySystem);
@@ -97,8 +122,13 @@
     PropertySystem& getPropertySystem() { return m_PropertySystem; }
     
     JSObject* createJSProperty(ScriptContext& _ctx, boost::shared_ptr<PropertyNode> _node);
+    std::string produceListenerID();
+    void addListener(PropertyScriptListener* _pListener);
+    void removeListener(const std::string& _identifier);
   private:
     PropertySystem& m_PropertySystem;
+    boost::ptr_vector<PropertyScriptListener> m_Listeners;
+    int m_NextListenerID;
   }; // PropertyScriptExtension
 
 }

Modified: dss/trunk/tests/modeljstests.cpp
===================================================================
--- dss/trunk/tests/modeljstests.cpp	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/tests/modeljstests.cpp	2009-09-16 14:29:05 UTC (rev 8766)
@@ -198,4 +198,58 @@
   BOOST_CHECK_EQUAL(ctx->evaluateScript<int>("getProperty('/testing')"), 2);
 }
 
+BOOST_AUTO_TEST_CASE(testPropertyListener) {
+  PropertySystem propSys;
+  boost::scoped_ptr<ScriptEnvironment> env(new ScriptEnvironment());
+  env->initialize();
+  ScriptExtension* ext = new PropertyScriptExtension(propSys);
+  env->addExtension(ext);
+
+  boost::scoped_ptr<ScriptContext> ctx(env->getContext());
+  ctx->evaluateScript<void>("setProperty('/testing', 1); setProperty('/triggered', false); "
+                            "listener_ident = setListener('/testing', function(changedNode) { setProperty('/triggered', true); }); "
+      );
+
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), false);
+  BOOST_CHECK_EQUAL(propSys.getIntValue("/testing"), 1);
+
+  propSys.setIntValue("/testing", 2);
+
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), true);
+  BOOST_CHECK_EQUAL(propSys.getIntValue("/testing"), 2);
+
+  // check that removing works
+  ctx->evaluateScript<void>("removeListener(listener_ident);");
+
+  propSys.setBoolValue("/triggered", false);
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), false);
+
+  propSys.setIntValue("/testing", 2);
+
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), false);
+  BOOST_CHECK_EQUAL(propSys.getIntValue("/testing"), 2);
+
+  // check that closures are working as expected
+  ctx->evaluateScript<void>("setProperty('/triggered', false); "
+                            "var ident = setListener('/testing', function(changedNode) { setProperty('/triggered', true); removeListener(ident); }); "
+      );
+
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), false);
+
+  propSys.setIntValue("/testing", 2);
+
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), true);
+  BOOST_CHECK_EQUAL(propSys.getIntValue("/testing"), 2);
+
+  propSys.setBoolValue("/triggered", false);
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), false);
+
+  propSys.setIntValue("/testing", 2);
+
+  BOOST_CHECK_EQUAL(propSys.getBoolValue("/triggered"), false);
+  BOOST_CHECK_EQUAL(propSys.getIntValue("/testing"), 2);
+
+
+}
+
 BOOST_AUTO_TEST_SUITE_END()

Modified: dss/trunk/tests/propertysystemtests.cpp
===================================================================
--- dss/trunk/tests/propertysystemtests.cpp	2009-09-16 13:06:59 UTC (rev 8765)
+++ dss/trunk/tests/propertysystemtests.cpp	2009-09-16 14:29:05 UTC (rev 8766)
@@ -230,4 +230,32 @@
   BOOST_CHECK(propSys->getProperty("/config/zone2/device1/isOn") == NULL);
 } // testAliases
 
+class TestListener : public PropertyListener {
+public:
+  TestListener(PropertySystem& _system, const std::string& _path)
+  : m_System(_system), m_Path(_path) {}
+
+  virtual void propertyChanged(PropertyNodePtr _pChangedNode) {
+    m_System.setBoolValue(m_Path, true);
+  }
+private:
+  PropertySystem& m_System;
+  const std::string& m_Path;
+};
+
+BOOST_AUTO_TEST_CASE(testListener) {
+  const std::string kTriggerPath = "/triggered";
+  boost::scoped_ptr<PropertySystem> propSys(new PropertySystem());
+  PropertyNodePtr node = propSys->createProperty("/testing");
+  boost::scoped_ptr<TestListener> listener(new TestListener(*propSys, kTriggerPath));
+  node->addListener(listener.get());
+
+  propSys->setBoolValue(kTriggerPath, false);
+  BOOST_CHECK_EQUAL(propSys->getBoolValue(kTriggerPath), false);
+
+  node->setIntegerValue(1);
+
+  BOOST_CHECK_EQUAL(propSys->getBoolValue(kTriggerPath), true);
+} // testListener
+
 BOOST_AUTO_TEST_SUITE_END()



More information about the dss-commits mailing list