Mercurial > code
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);