changeset 399:73517fd307dd

Js: - Add Global tag class to push global object, - Add FunctionMap to push a list of functions to the object at the top, - Add TypeInfoPointer helper for pointers.
author David Demelier <markand@malikania.fr>
date Thu, 01 Oct 2015 14:02:28 +0200
parents 94bfe7ba9a13
children c118df15d354
files C++/modules/Js/Js.h C++/tests/Js/main.cpp
diffstat 2 files changed, 192 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Js/Js.h	Wed Sep 30 19:59:12 2015 +0200
+++ b/C++/modules/Js/Js.h	Thu Oct 01 14:02:28 2015 +0200
@@ -34,6 +34,7 @@
 #include <memory>
 #include <string>
 #include <type_traits>
+#include <unordered_map>
 
 #include <duktape.h>
 
@@ -54,13 +55,20 @@
 };
 
 /**
- * @class Arary
+ * @class Array
  * @brief Empty class tag for push() function
  */
 class Array {
 };
 
 /**
+ * @class Global
+ * @brief Empty class tag to push the global object
+ */
+class Global {
+};
+
+/**
  * @class Function
  * @brief Duktape/C function definition
  */
@@ -78,6 +86,11 @@
 };
 
 /**
+ * Map of functions to set on an object.
+ */
+using FunctionMap = std::unordered_map<std::string, Function>;
+
+/**
  * @class ErrorInfo
  * @brief Error description
  *
@@ -849,6 +862,16 @@
 	}
 };
 
+template <>
+class TypeInfo<FunctionMap> {
+public:
+	static void push(Context &ctx, const FunctionMap &map)
+	{
+		for (const auto &entry : map)
+			ctx.putProperty(-1, entry.first, entry.second);
+	}
+};
+
 /**
  * @class TypeInfo<Object>
  * @brief Default implementation for Object
@@ -883,6 +906,15 @@
 	}
 };
 
+template <>
+class TypeInfo<Global> {
+public:
+	static void push(Context &ctx, const Global &)
+	{
+		duk_push_global_object(ctx);
+	}
+};
+
 /* ------------------------------------------------------------------
  * Helpers for pointers and std::shared_ptr
  * ------------------------------------------------------------------ */
@@ -963,6 +995,86 @@
 	return value;
 }
 
+/* ------------------------------------------------------------------
+ * Helpers for pointer
+ * ------------------------------------------------------------------ */
+
+/**
+ * @class TypeInfoPointer
+ * @brief Generates push / get / require / optional / construct for custom pointer object
+ *
+ * Specialize TypeInfo<T *> and inherits from this class to implement the push(),
+ * get(), require() and optional() functions.
+ *
+ * You only need to implement `static void prototype(Context &ctx)` which must push the prototype
+ * to use for the underlying object.
+ *
+ * This class can be used to both push and construct pointers objects and is useful when you want to push C++
+ * objects that will be only needed into JavaScript side.
+ */
+template <typename T>
+class TypeInfoPointer {
+private:
+	static void apply(Context &ctx, T *value);
+
+public:
+	static void construct(Context &ctx, T *value);
+	static void push(Context &ctx, T *value);
+	static T *get(Context &ctx, int index);
+};
+
+template <typename T>
+void TypeInfoPointer<T>::apply(Context &ctx, T *value)
+{
+	duk_push_boolean(ctx, false);
+	duk_put_prop_string(ctx, -2, "\xff""\xff""js-deleted");
+	duk_push_pointer(ctx, value);
+	duk_put_prop_string(ctx, -2, "\xff""\xff""js-ptr");
+	TypeInfo<T *>::prototype(ctx);
+	duk_set_prototype(ctx, -2);
+	duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t {
+		duk_get_prop_string(ctx, 0, "\xff""\xff""js-deleted");
+
+		if (duk_to_boolean(ctx, -1)) {
+			duk_push_boolean(ctx, true);
+			duk_put_prop_string(ctx, 0, "\xff""\xff""js-deleted");
+			duk_get_prop_string(ctx, 0, "\xff""\xff""js-ptr");
+			delete static_cast<T *>(duk_to_pointer(ctx, -1));
+			duk_pop(ctx);
+		}
+
+		duk_pop(ctx);
+
+		return 0;
+	}, 1);
+	duk_set_finalizer(ctx, -2);
+}
+
+template <typename T>
+void TypeInfoPointer<T>::construct(Context &ctx, T *value)
+{
+	duk_push_this(ctx);
+	apply(ctx, value);
+	duk_pop(ctx);
+}
+
+template <typename T>
+void TypeInfoPointer<T>::push(Context &ctx, T *value)
+{
+	duk_push_object(ctx);
+	apply(ctx, value);
+}
+
+template <typename T>
+T *TypeInfoPointer<T>::get(Context &ctx, int index)
+{
+	duk_get_prop_string(ctx, index, "\xff""\xff""js-ptr");
+	T *value = static_cast<T *>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	return value;
+}
+
 } // !js
 
 #endif // !_JS_H_
--- a/C++/tests/Js/main.cpp	Wed Sep 30 19:59:12 2015 +0200
+++ b/C++/tests/Js/main.cpp	Thu Oct 01 14:02:28 2015 +0200
@@ -221,9 +221,7 @@
 {
 	Context context;
 
-	context.setGlobal("f", Function{[] (ContextPtr pctx) -> duk_idx_t {
-		Context ctx{pctx};
-
+	context.setGlobal("f", Function{[] (Context &ctx) -> duk_idx_t {
 		ctx.raise(Error{"error thrown"});
 
 		return 0;
@@ -244,7 +242,7 @@
 }
 
 /* --------------------------------------------------------
- * TypeInfo with shared
+ * TypeInfo with shared and pointer
  * -------------------------------------------------------- */
 
 class Player {
@@ -252,6 +250,8 @@
 	bool m_updated{false};
 };
 
+namespace js {
+
 template <>
 class TypeInfo<std::shared_ptr<Player>> : public TypeInfoShared<Player> {
 public:
@@ -261,14 +261,33 @@
 		ctx.push(Object{});
 
 		/* "update" method */
-		ctx.putProperty(-1, "update", Function{[] (ContextPtr pctx) -> duk_ret_t {
-			Context{pctx}.self<std::shared_ptr<Player>>()->m_updated = true;
+		ctx.putProperty(-1, "update", Function{[] (Context &ctx) -> int {
+			ctx.self<std::shared_ptr<Player>>()->m_updated = true;
 
 			return 0;
 		}});
 	}
 };
 
+template <>
+class TypeInfo<Player *> : public TypeInfoPointer<Player> {
+public:
+	static void prototype(Context &ctx)
+	{
+		/* Create a temporary for the test */
+		ctx.push(Object{});
+
+		/* "update" method */
+		ctx.putProperty(-1, "update", Function{[] (Context &ctx) -> int {
+			ctx.self<Player *>()->m_updated = true;
+
+			return 0;
+		}});
+	}
+};
+
+} // !js
+
 TEST(Shared, simple)
 {
 	Context ctx;
@@ -297,6 +316,60 @@
 	}
 }
 
+TEST(Pointer, simple)
+{
+	Context ctx;
+	Player *p{new Player};
+
+	ctx.push(p);
+	Player *p2 = ctx.get<Player *>(-1);
+
+	p2->m_updated = true;
+
+	ASSERT_TRUE(p->m_updated);
+}
+
+TEST(Pointer, self)
+{
+	Context ctx;
+	Player *p{new Player};
+
+	try {
+		ctx.setGlobal("player", p);
+		ctx.peval(Script{"player.update();"});
+
+		ASSERT_TRUE(p->m_updated);
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+/* --------------------------------------------------------
+ * FunctionMap
+ * -------------------------------------------------------- */
+
+TEST(PushFunctionMap, basic)
+{
+	bool f1called{false}, f2called{false};
+	auto f1 = [&] (Context &) -> int { f1called = true; return 0; };
+	auto f2 = [&] (Context &) -> int { f2called = true; return 0; };
+
+	FunctionMap map{
+		{ "f1", { f1, 0 }},
+		{ "f2", { f2, 0 }}
+	};
+	Context ctx;
+
+	ctx.push(Global{});
+	ctx.push(map);
+	ctx.pop();
+	ctx.peval(Script{"f1();"});
+	ctx.peval(Script{"f2();"});
+
+	ASSERT_TRUE(f1called);
+	ASSERT_TRUE(f2called);
+}
+
 int main(int argc, char **argv)
 {
 	testing::InitGoogleTest(&argc, argv);