changeset 397:6b2db5425836

Js: initial support for objects as shared_ptr
author David Demelier <markand@malikania.fr>
date Tue, 29 Sep 2015 12:50:28 +0200
parents 30788c97c58c
children 94bfe7ba9a13
files C++/modules/Js/Js.h C++/tests/Js/main.cpp
diffstat 2 files changed, 145 insertions(+), 221 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Js/Js.h	Mon Sep 28 19:40:26 2015 +0200
+++ b/C++/modules/Js/Js.h	Tue Sep 29 12:50:28 2015 +0200
@@ -149,17 +149,24 @@
 
 
 public:
+	/**
+	 * Create default context.
+	 */
 	inline Context()
 		: m_handle{duk_create_heap_default(), duk_destroy_heap}
 	{
 	}
 
+	/**
+	 * Create borrowed context that will not be deleted.
+	 *
+	 * @param ctx the pointer to duk_context
+	 */
 	inline Context(ContextPtr ctx) noexcept
 		: m_handle{ctx, [] (ContextPtr) {}}
 	{
 	}
 
-#if 0
 	/**
 	 * Convert the context to the native Duktape/C type.
 	 *
@@ -167,7 +174,7 @@
 	 */
 	inline operator duk_context *() noexcept
 	{
-		return get();
+		return m_handle.get();
 	}
 
 	/**
@@ -177,156 +184,22 @@
 	 */
 	inline operator duk_context *() const noexcept
 	{
-		return get();
-	}
-#endif
-
-	/* --------------------------------------------------------
-	 * Push functions
-	 * -------------------------------------------------------- */
-
-#if 0
-
-	/**
-	 * Push a boolean.
-	 *
-	 * @param ctx the context
-	 * @param value the boolean value
-	 */
-	inline void push(bool value)
-	{
-		duk_push_boolean(m_handle.get(), value);
-	}
-
-	/**
-	 * Push an integer.
-	 *
-	 * @param ctx the context
-	 * @param value the integer value
-	 */
-	inline void push(int value)
-	{
-		duk_push_int(m_handle.get(), value);
-	}
-
-	/**
-	 * Push a real value.
-	 *
-	 * @param ctx the context
-	 * @param value the value
-	 */
-	void push(double value)
-	{
-		duk_push_number(m_handle.get(), value);
-	}
-
-	/**
-	 * Push a string.
-	 *
-	 * @param ctx the context
-	 * @param value the string
-	 */
-	void push(const std::string &value)
-	{
-		duk_push_lstring(m_handle.get(), value.c_str(), value.length());
-	}
-
-	/**
-	 * Push a literal or C-string.
-	 *
-	 * @param ctx the context
-	 * @param value the value
-	 */
-	void push(const char *value)
-	{
-		duk_push_string(m_handle.get(), value);
+		return m_handle.get();
 	}
 
-	/**
-	 * Push a Duktape/C function.
-	 *
-	 * @param ctx the context
-	 * @param function the function
-	 */
-	void push(const Function &fn)
-	{
-		assert(fn.function);
-
-		duk_push_c_function(m_handle.get(), fn.function, fn.nargs);
-	}
-
-	/**
-	 * Push an empty object on the stack.
-	 *
-	 * @param ctx the context
-	 * @param object the empty object
-	 */
-	void push(const Object &)
-	{
-		duk_push_object(m_handle.get());
-	}
-
-	/**
-	 * Push an empty array at the top of the stack.
-	 *
-	 * @param ctx the context
-	 * @param array the empty array
-	 */
-	void push(const Array &)
-	{
-		duk_push_array(m_handle.get());
-	}
-
-	/* --------------------------------------------------------
-	 * Get functions
-	 * -------------------------------------------------------- */
-
-	/**
-	 * Get a boolean.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void get(ContextPtr ctx, duk_idx_t index, bool &value);
-
-	/**
-	 * Get an integer.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void get(ContextPtr ctx, duk_idx_t index, int &value);
-
-	/**
-	 * Get a real.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void get(ContextPtr ctx, duk_idx_t index, double &value);
-
-	/**
-	 * Get a string.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void get(ContextPtr ctx, duk_idx_t index, std::string &value);
-
-#endif
-
 	/* ----------------------------------------------------------
 	 * Push / Get / Require
 	 * ---------------------------------------------------------- */
 
+	/**
+	 * Push a value into the stack. Calls TypeInfo<T>::push(*this, value);
+	 *
+	 * @param value the value to forward
+	 */
 	template <typename Type>
 	inline void push(Type &&value)
 	{
-		TypeInfo<std::decay_t<Type>>::push(m_handle.get(), std::forward<Type>(value));
+		TypeInfo<std::decay_t<Type>>::push(*this, std::forward<Type>(value));
 	}
 
 	/**
@@ -336,15 +209,15 @@
 	 * @return the value
 	 */
 	template <typename Type>
-	inline Type get(duk_idx_t index)
+	inline auto get(duk_idx_t index) -> decltype(TypeInfo<Type>::get(*this, 0))
 	{
-		return TypeInfo<Type>::get(m_handle.get(), index);
+		return TypeInfo<Type>::get(*this, index);
 	}
 
 	template <typename Type>
-	inline Type require(duk_idx_t index)
+	inline auto require(duk_idx_t index) -> decltype(TypeInfo<Type>::require(*this, 0))
 	{
-		return TypeInfo<Type>::require(m_handle.get(), index);
+		return TypeInfo<Type>::require(*this, index);
 	}
 
 	/* --------------------------------------------------------
@@ -352,11 +225,11 @@
 	 * -------------------------------------------------------- */
 
 	template <typename Type>
-	inline Type getObject(duk_idx_t index, const std::string &name)
+	inline auto getObject(duk_idx_t index, const std::string &name) -> decltype(get<Type>(0))
 	{
 		assertBegin(m_handle.get());
 		duk_get_prop_string(m_handle.get(), index, name.c_str());
-		Type value = get<Type>(-1);
+		auto &&value = get<Type>(-1);
 		duk_pop(m_handle.get());
 		assertEquals(m_handle.get());
 
@@ -379,66 +252,6 @@
 	}
 
 	/* --------------------------------------------------------
-	 * Require functions
-	 * -------------------------------------------------------- */
-
-#if 0
-
-	/**
-	 * Requires a value or throw an exception if not a boolean.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void require(ContextPtr ctx, duk_idx_t index, bool &value);
-
-	/**
-	 * Requires a value or throw an exception if not an integer.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void require(ContextPtr ctx, duk_idx_t index, int &value);
-
-	/**
-	 * Requires a value or throw an exception if not a double.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store the value
-	 */
-	void require(ContextPtr ctx, duk_idx_t index, double &value);
-
-	/**
-	 * Requires a value or throw an exception if not a string.
-	 *
-	 * @param ctx the context
-	 * @param index the index
-	 * @param value the reference where to store string
-	 */
-	void require(ContextPtr ctx, duk_idx_t index, std::string &value);
-
-	/**
-	 * Generic function to require a value from the stack.
-	 *
-	 * @param ctx the context
-	 * @param index the specified index
-	 * @return the value
-	 */
-	template <typename Type>
-	inline Type require(ContextPtr ctx, duk_idx_t index)
-	{
-		Type value;
-
-		require(ctx, index, value);
-
-		return value;
-	}
-#endif
-
-	/* --------------------------------------------------------
 	 * Basic functions
 	 * -------------------------------------------------------- */
 
@@ -589,11 +402,11 @@
 	 * @return the value
 	 */
 	template <typename Type>
-	inline Type getGlobal(const std::string &name)
+	inline auto getGlobal(const std::string &name) -> decltype(get<Type>(0))
 	{
 		assertBegin(m_handle.get());
 		duk_get_global_string(m_handle.get(), name.c_str());
-		Type value = get<Type>(-1);
+		auto &&value = get<Type>(-1);
 		duk_pop(m_handle.get());
 		assertEquals(m_handle.get());
 
@@ -631,6 +444,16 @@
 		duk_put_prop_string(m_handle.get(), -2, "name");
 		duk_throw(m_handle.get());
 	}
+
+	template <typename T>
+	inline auto self() -> decltype(TypeInfo<T>::get(*this, 0))
+	{
+		duk_push_this(m_handle.get());
+		auto &&value = TypeInfo<T>::get(*this, -1);
+		duk_pop(m_handle.get());
+
+		return value;
+	}
 };
 
 /* ------------------------------------------------------------------
@@ -756,12 +579,12 @@
 template <>
 class TypeInfo<bool> {
 public:
-	static bool get(ContextPtr ctx, duk_idx_t index)
+	static bool get(Context &ctx, duk_idx_t index)
 	{
 		return duk_get_boolean(ctx, index);
 	}
 
-	static void push(ContextPtr ctx, bool value)
+	static void push(Context &ctx, bool value)
 	{
 		duk_push_boolean(ctx, value);
 	}
@@ -770,12 +593,12 @@
 template <>
 class TypeInfo<double> {
 public:
-	static double get(ContextPtr ctx, duk_idx_t index)
+	static double get(Context &ctx, duk_idx_t index)
 	{
 		return duk_get_number(ctx, index);
 	}
 
-	static void push(ContextPtr ctx, double value)
+	static void push(Context &ctx, double value)
 	{
 		duk_push_number(ctx, value);
 	}
@@ -784,7 +607,7 @@
 template <>
 class TypeInfo<std::string> {
 public:
-	static std::string get(ContextPtr ctx, duk_idx_t index)
+	static std::string get(Context &ctx, duk_idx_t index)
 	{
 		duk_size_t size;
 		const char *text = duk_get_lstring(ctx, index, &size);
@@ -792,7 +615,7 @@
 		return std::string{text, size};
 	}
 
-	static void push(ContextPtr ctx, const std::string &value)
+	static void push(Context &ctx, const std::string &value)
 	{
 		duk_push_lstring(ctx, value.c_str(), value.length());
 	}
@@ -801,7 +624,7 @@
 template <>
 class TypeInfo<const char *> {
 public:
-	static void push(ContextPtr ctx, const char *value)
+	static void push(Context &ctx, const char *value)
 	{
 		duk_push_string(ctx, value);
 	}
@@ -810,7 +633,7 @@
 template <>
 class TypeInfo<Function> {
 public:
-	static void push(ContextPtr ctx, const Function &fn)
+	static void push(Context &ctx, const Function &fn)
 	{
 		assert(fn.function);
 
@@ -821,7 +644,7 @@
 template <>
 class TypeInfo<Object> {
 public:
-	static void push(ContextPtr ctx, const Object &)
+	static void push(Context &ctx, const Object &)
 	{
 		duk_push_object(ctx);
 	}
@@ -830,12 +653,59 @@
 template <>
 class TypeInfo<Array> {
 public:
-	static void push(ContextPtr ctx, const Array &)
+	static void push(Context &ctx, const Array &)
 	{
 		duk_push_array(ctx);
 	}
 };
 
+/* ------------------------------------------------------------------
+ * Helpers for pointers and std::shared_ptr
+ * ------------------------------------------------------------------ */
+
+/**
+ * @class TypeInfoShared
+ * @brief Generates push / get / require for std::shared_ptr<T>
+ *
+ * Specialize TypeInfo<std::shared_ptr<T>> and inherits from this class to implement the push(),
+ * get() and require() functions.
+ *
+ * You only need to implement `static void prototype(Context &ctx)` which must push the prototype
+ * to use for the underlying object.
+ */
+template <typename T>
+class TypeInfoShared {
+public:
+	static void push(Context &ctx, std::shared_ptr<T> value);
+	static std::shared_ptr<T> get(Context &ctx, duk_idx_t index);
+};
+
+template <typename T>
+void TypeInfoShared<T>::push(Context &ctx, std::shared_ptr<T> value)
+{
+	duk_push_object(ctx);
+	duk_push_boolean(ctx, false);
+	duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted");
+	duk_push_pointer(ctx, new std::shared_ptr<T>(value));
+	duk_put_prop_string(ctx, -2, "\xff""\xff""js-shared-ptr");
+
+	TypeInfo<std::shared_ptr<T>>::prototype(ctx);
+
+	// TODO: set deleter
+
+	duk_set_prototype(ctx, -2);
+}
+
+template <typename T>
+std::shared_ptr<T> TypeInfoShared<T>::get(Context &ctx, duk_idx_t index)
+{
+	duk_get_prop_string(ctx, index, "\xff""\xff""js-shared-ptr");
+	std::shared_ptr<T> value = *static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	return value;
+}
+
 } // !js
 
 #endif // !_JS_H_
--- a/C++/tests/Js/main.cpp	Mon Sep 28 19:40:26 2015 +0200
+++ b/C++/tests/Js/main.cpp	Tue Sep 29 12:50:28 2015 +0200
@@ -247,6 +247,60 @@
 	ASSERT_EQ("error thrown", context.getGlobal<std::string>("message"));
 }
 
+/* --------------------------------------------------------
+ * TypeInfo with shared
+ * -------------------------------------------------------- */
+
+class Player {
+public:
+	bool m_updated{false};
+};
+
+template <>
+class TypeInfo<std::shared_ptr<Player>> : public TypeInfoShared<Player> {
+public:
+	static void prototype(Context &ctx)
+	{
+		/* Create a temporary for the test */
+		ctx.push(Object{});
+
+		/* "update" method */
+		ctx.setObject(-1, "update", Function{[] (ContextPtr pctx) -> duk_ret_t {
+			Context{pctx}.self<std::shared_ptr<Player>>()->m_updated = true;
+
+			return 0;
+		}});
+	}
+};
+
+TEST(Shared, simple)
+{
+	Context ctx;
+	std::shared_ptr<Player> p{new Player};
+
+	ctx.push(p);
+	std::shared_ptr<Player> p2 = ctx.get<std::shared_ptr<Player>>(-1);
+
+	p2->m_updated = true;
+
+	ASSERT_TRUE(p->m_updated);
+}
+
+TEST(Shared, self)
+{
+	Context ctx;
+	std::shared_ptr<Player> p{new Player};
+
+	try {
+		ctx.setGlobal("player", p);
+		ctx.pevalString("player.update();");
+
+		ASSERT_TRUE(p->m_updated);
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
 int main(int argc, char **argv)
 {
 	testing::InitGoogleTest(&argc, argv);