[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