From 560be0817b3231955789d002fe5fb194962deb88 Mon Sep 17 00:00:00 2001 From: Thorsten Beier Date: Wed, 27 Mar 2024 07:58:16 +0100 Subject: [PATCH] Docs design (#62) * design section * embed * pin xeus --- build_mkdocs.sh | 2 +- docs/design.md | 97 ++++++++++++++++++++++++++++++++++++++++++++ docs/embed.md | 46 +++++++++++++++++++++ docs/installation.md | 2 +- mkdocs.yml | 1 + 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 docs/design.md create mode 100644 docs/embed.md diff --git a/build_mkdocs.sh b/build_mkdocs.sh index 1ec94cd..7396f91 100755 --- a/build_mkdocs.sh +++ b/build_mkdocs.sh @@ -29,7 +29,7 @@ if [ ! -d "$WASM_ENV_PREFIX" ]; then --yes \ python pybind11 nlohmann_json pybind11_json numpy \ bzip2 sqlite zlib libffi exceptiongroup \ - xeus xeus-lite xeus-python "xeus-javascript>=0.3.2" xtl "ipython=8.22.2=py311had7285e_1" "traitlets>=5.14.2" + "xeus<4" "xeus-lite<2" xeus-python "xeus-javascript>=0.3.2" xtl "ipython=8.22.2=py311had7285e_1" "traitlets>=5.14.2" else echo "Wasm env $WASM_ENV_NAME already exists" diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..c8a090f --- /dev/null +++ b/docs/design.md @@ -0,0 +1,97 @@ +# Design + +## Main Idea +### pybind11 +[Pybind11](https://github.com/pybind/pybind11) is a library that exposes C++ types in Python. It is a wrapper around the Python C API that allows for seamless integration of C++ and Python. +To export a C++ class like the following to Python, you would use pybind11: + +```C++ +// foo.h +class Foo { +public: + void say_hello() { + std::cout << "Hello, World!" << std::endl; + } +}; +``` + +```C++ +// main.cpp +#include +#include + +PYBIND11_MODULE(example, m) { + py::class_(m, "Foo") + .def(py::init<>()) + .def("say_hello", &Foo::say_hello); +} +``` +Not only can Python call C++ functions, but C++ can also call Python functions. In particular, one can interact +with Python objects. An object is represented by the `py::object` type on the C++ side. + +```C++ +// main.cpp +py::object sys = py::module::import("sys"); +py::object version = sys.attr("version"); +std::string version_string = version.cast(); +std::cout << "Python version: " << version_string << std::endl; + +``` + + + + +### embind +There is a simmilar for emscripten called [embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html). It allows you to expose C++ types to JavaScript. + +```C++ +// main.cpp +#include +#include +using namespace emscripten; +// Binding code +EMSCRIPTEN_BINDINGS(my_class_example) { + class_("Foo") + .constructor<>() + .function("say_hello", &Foo::say_hello) + ; +} +``` +To access JavasScript from C++, you would use the `emscripten::val` type. This is the pendant to `py::object` in pybind11. + +```C++ +emscripten::val console = emscripten::val::global("console"); +console.call("log", "Hello, World!"); +``` + +### pyjs +The main idea of pyjs is, to export emscripten `emscripten::val` objects to Python with pybind11 and to export pybind11 `py::object` objects to JavaScript with embind. + +That way, we get a seamless integration of Python and JavaScript with relatively little effort and high level C++ +code. + + + +## Error Handling + +To catch JavaScript exceptions from Python, we wrap all JavaScript code in a try-catch block. If an exception is thrown, we catch it and raise a Python exception with the same message. +The Python exceptions are directly translated to JavaScript exceptions. + + + +## Memory Management +Any C++ class that is exported via embind needs to be deleted by hand with `delete` method. This is because the JavaScript garbage collector does not know about the C++ objects. +Therefore all `py::object` objects that are created from javascript objects need to be deleted by hand. This is done by calling the `delete` method on the `pyobject` object JavaScript side. + + +## Performance + +Compared to pyodide, pyjs is slowwer when crossing the language barrier. Yet itm is fast enough for all practical purposes. + +## Packaging + +Pyjs is exclusively via [emscripten-forge](https://github.com/emscripten-forge/recipes). + +## Testing + +To test pyjs without manually inspecting a web page, we use [pyjs-code-runner](https://github.com/emscripten-forge/pyjs-code-runner). This is a tool that runs a Python script in a headless browser and returns the output. diff --git a/docs/embed.md b/docs/embed.md new file mode 100644 index 0000000..e2c3809 --- /dev/null +++ b/docs/embed.md @@ -0,0 +1,46 @@ +# Embedding pyjs in C++ +Not only can `pyjs` be used as a standalone Python interpreter, but it can also be embedded in a C++ program. This allows you to run Python code in a C++ program. +Pyjs is compiled as a static library that can be linked to a C++ program (when compiled with emscripten). + + +To include `pyjs` in a C++ program, the following code is needed: + +```C++ + +#include +#include + +#include +#include + +// export the python core module of pyjs +PYBIND11_EMBEDDED_MODULE(pyjs_core, m) { + pyjs::export_pyjs_module(m); +} + +// export the javascript module of pyjs +EMSCRIPTEN_BINDINGS(my_module) { + pyjs::export_js_module(); +} + +``` + +In the CMakelists.txt file, the following lines are needed to link the `pyjs` library: + +```CMake +find_package(pyjs ${pyjs_REQUIRED_VERSION} REQUIRED) + +target_link_libraries(my_target PRIVATE pyjs) + +target_link_options(my_target + PUBLIC "SHELL: -s LZ4=1" + PUBLIC "SHELL: --post-js ${pyjs_PRO_JS_PATH}" + PUBLIC "SHELL: --pre-js ${pyjs_PRE_JS_PATH}" + PUBLIC "SHELL: -s MAIN_MODULE=1" + PUBLIC "SHELL: -s WASM_BIGINT" + PUBLIC "-s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=\"['\$Browser', '\$ERRNO_CODES']\" " +) +``` +As described in the [deployment](../installation) section, the needs to be packed. +See the [pack the environment](../installation/#pack-the-environment)-section for instructions on to pack the environment with [`empack`](https://github.com/emscripten-forge/empack). + diff --git a/docs/installation.md b/docs/installation.md index 22b8128..ceec2aa 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,4 +1,4 @@ -# Installation +# Deploying pyjs ## Prerequisites: diff --git a/mkdocs.yml b/mkdocs.yml index 77d5e69..120bbf4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ theme: plugins: +- autorefs - mkdocstrings: handlers: python: