Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions module/pyjs/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import ast
import pyjs_core
from pyjs_core import JsValue, js_array, js_py_object
import warnings
import time

def install_submodules():
def _js_mod__getattr__(name: str) -> Any:
Expand Down Expand Up @@ -307,3 +309,49 @@ async def async_exec_eval(stmts, globals=None, locals=None):
parsed_fn.body[0].body = parsed_stmts.body
exec(compile(parsed_fn, filename="<ast>", mode="exec"), globals, locals)
return await eval(f'{fn_name}()', globals, locals) # fmt: skip



class _CallbackEntryPoint:
def __init__(self, py_callback):
self._py_callback = py_callback
self._last_time = time.time()
def __call__(self):
t = time.time()
dt = t - self._last_time
self._last_time = t
self._py_callback(dt)

_callback_entry_point = None


def set_main_loop_callback(py_callback, fps=0):

global _callback_entry_point
if _callback_entry_point is not None:
# show a warning if the callback is already set
warnings.warn(""" A main loop callback is already set.
This will be replaced by the new callback,
use cancel_main_loop before setting a new callback to avoid this warning
""",
UserWarning)

cancel_main_loop()

_callback_entry_point = _CallbackEntryPoint(py_callback)

pyjs_core._set_main_loop_callback(
_callback_entry_point,
int(fps)
)


def cancel_main_loop():
"""Cancel the main loop callback."""
global _callback_entry_point
if _callback_entry_point is not None:
pyjs_core._cancel_main_loop()
pyjs_core._set_noop_main_loop()
_callback_entry_point = None
else:
warnings.warn("No main loop callback is set to cancel.", UserWarning)
105 changes: 89 additions & 16 deletions src/export_pyjs_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,98 @@ namespace py = pybind11;
namespace em = emscripten;

namespace pyjs
{
{

// struct LoopContext
// {
// py::object m_callback;
// bool m_cancel_loop_on_error = true; // default to true
// bool m_exit_loop = false;

// LoopContext(py::object callback, bool cancel_loop_on_error)
// : m_callback(std::move(callback)), m_cancel_loop_on_error(cancel_loop_on_error), m_exit_loop(false) {}
// };


void wrapped_callback(void* cb_ptr) {
// Reinterpret the void pointer back to a PyObject pointer
auto py_object = reinterpret_cast<PyObject*>(cb_ptr);
if(!py_object) {
std::cerr << "Error: callback pointer is null." << std::endl;
}
// We can use PyObject_CallObject to call the Python function
if (PyObject_CallNoArgs(py_object) == nullptr) {
// If the call fails, we can print the error
std::cerr << "Error calling Python callback:" << std::endl;
PyErr_Print();
}
};

void noop_callback() {
// This is a no-op callback, it does nothing

// we see a strange error when we run emscripten_cancel_main_loop
// **WITHOUT setting a new loop right away**
// so instead of just cancelling the loop, we need
// to cancel and right away set a new loop
}

void self_cancel_callback() {
emscripten_cancel_main_loop();
};


void export_main_loop_callbacks(py::module_& pyjs_module)
{



// // class for loop context
// py::class_<LoopContext>(pyjs_module, "LoopContext")
// .def(py::init<py::object, bool>(), py::arg("callback"), py::arg("cancel_loop_on_error") = true)
// .def_readwrite("exit_loop", &LoopContext::m_exit_loop)
// ;



// Export main loop callbacks
pyjs_module.def("_set_main_loop_callback", [](py::handle callback, int fps) {

// get a PyObject * from the handle
auto py_object = callback.ptr();

// convert the PyObject to void*
void* callback_ptr = reinterpret_cast<void*>(py_object);


// use emscripten_set_main_loop_arg
emscripten_set_main_loop_arg(
wrapped_callback,
callback_ptr, // pass the callback pointer as argument
fps, // frames per second
false
);
});

// explicit cancel main loop
pyjs_module.def("_cancel_main_loop", []() {
// This will cancel the main loop if it is currently running
emscripten_cancel_main_loop();
});

pyjs_module.def("_set_noop_main_loop", []() {
// This will set a no-op main loop
emscripten_set_main_loop(noop_callback, 1, false); // set a no-op loop to avoid errors
});

}



void export_pyjs_module(py::module_& pyjs_module)
{
export_js_proxy(pyjs_module);
try
{
// pyjs_core_pseudo_init(pyjs_module);
// pyjs_extend_js_val_pseudo_init(pyjs_module);
// pyjs_error_handling_pseudo_init(pyjs_module);
// pyjs_convert_pseudo_init(pyjs_module);
// pyjs_convert_py_to_js_pseudo_init(pyjs_module);
// pyjs_webloop_pseudo_init(pyjs_module);
// pyjs_pyodide_polyfill_pseudo_init(pyjs_module);
}
catch (py::error_already_set& e)
{
std::cout << "error: " << e.what() << "\n";
throw e;
}
export_main_loop_callbacks(pyjs_module);

}
}