Skip to content

Commit 5261c53

Browse files
committed
loop
1 parent b7f3a08 commit 5261c53

File tree

2 files changed

+182
-16
lines changed

2 files changed

+182
-16
lines changed

module/pyjs/core.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import ast
77
import pyjs_core
88
from pyjs_core import JsValue, js_array, js_py_object
9+
import warnings
10+
import time
911

1012
def install_submodules():
1113
def _js_mod__getattr__(name: str) -> Any:
@@ -307,3 +309,49 @@ async def async_exec_eval(stmts, globals=None, locals=None):
307309
parsed_fn.body[0].body = parsed_stmts.body
308310
exec(compile(parsed_fn, filename="<ast>", mode="exec"), globals, locals)
309311
return await eval(f'{fn_name}()', globals, locals) # fmt: skip
312+
313+
314+
315+
class _CallbackEntryPoint:
316+
def __init__(self, py_callback):
317+
self._py_callback = py_callback
318+
self._last_time = time.time()
319+
def __call__(self):
320+
t = time.time()
321+
dt = t - self._last_time
322+
self._last_time = t
323+
self._py_callback(dt)
324+
325+
_callback_entry_point = None
326+
327+
328+
def set_main_loop_callback(py_callback, fps=0):
329+
330+
global _callback_entry_point
331+
if _callback_entry_point is not None:
332+
# show a warning if the callback is already set
333+
warnings.warn(""" A main loop callback is already set.
334+
This will be replaced by the new callback,
335+
use cancel_main_loop before setting a new callback to avoid this warning
336+
""",
337+
UserWarning)
338+
339+
cancel_main_loop()
340+
341+
_callback_entry_point = _CallbackEntryPoint(py_callback)
342+
343+
pyjs_core._set_main_loop_callback(
344+
_callback_entry_point,
345+
int(fps)
346+
)
347+
348+
349+
def cancel_main_loop():
350+
"""Cancel the main loop callback."""
351+
global _callback_entry_point
352+
if _callback_entry_point is not None:
353+
pyjs_core._cancel_main_loop()
354+
pyjs_core._set_noop_main_loop()
355+
_callback_entry_point = None
356+
else:
357+
warnings.warn("No main loop callback is set to cancel.", UserWarning)

src/export_pyjs_module.cpp

Lines changed: 134 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,143 @@ namespace py = pybind11;
1616
namespace em = emscripten;
1717

1818
namespace pyjs
19-
{
20-
void export_pyjs_module(py::module_& pyjs_module)
19+
{
20+
21+
struct LoopContext
2122
{
22-
export_js_proxy(pyjs_module);
23-
try
24-
{
25-
// pyjs_core_pseudo_init(pyjs_module);
26-
// pyjs_extend_js_val_pseudo_init(pyjs_module);
27-
// pyjs_error_handling_pseudo_init(pyjs_module);
28-
// pyjs_convert_pseudo_init(pyjs_module);
29-
// pyjs_convert_py_to_js_pseudo_init(pyjs_module);
30-
// pyjs_webloop_pseudo_init(pyjs_module);
31-
// pyjs_pyodide_polyfill_pseudo_init(pyjs_module);
23+
py::object m_callback;
24+
bool m_cancel_loop_on_error = true; // default to true
25+
bool m_exit_loop = false;
26+
27+
LoopContext(py::object callback, bool cancel_loop_on_error)
28+
: m_callback(std::move(callback)), m_cancel_loop_on_error(cancel_loop_on_error), m_exit_loop(false) {}
29+
};
30+
31+
32+
33+
34+
// void wrapped_callback_without_cancel(void* cb_ptr) {
35+
// // Reinterpret the void pointer back to a PyObject pointer
36+
// auto py_object = reinterpret_cast<PyObject*>(cb_ptr);
37+
// // We can use PyObject_CallObject to call the Python function
38+
// if (PyObject_CallNoArgs(py_object) == nullptr) {
39+
// // If the call fails, we can print the error
40+
// std::cerr << "Error calling Python callback." << std::endl;
41+
// PyErr_Print();
42+
43+
// }
44+
// };
45+
void wrapped_callback(void* cb_ptr) {
46+
// Reinterpret the void pointer back to a PyObject pointer
47+
auto py_object = reinterpret_cast<PyObject*>(cb_ptr);
48+
if(!py_object) {
49+
std::cerr << "Error: callback pointer is null." << std::endl;
3250
}
33-
catch (py::error_already_set& e)
34-
{
35-
std::cout << "error: " << e.what() << "\n";
36-
throw e;
51+
// We can use PyObject_CallObject to call the Python function
52+
if (PyObject_CallNoArgs(py_object) == nullptr) {
53+
// If the call fails, we can print the error
54+
std::cerr << "Error calling Python callback:" << std::endl;
55+
PyErr_Print();
3756
}
57+
};
58+
59+
void noop_callback() {
60+
// This is a no-op callback, it does nothing
61+
62+
// we see a strange error when we run emscripten_cancel_main_loop
63+
// **WITHOUT setting a new loop right away**
64+
// so instead of just cancelling the loop, we need
65+
// to cancel and right away set a new loop
66+
}
67+
68+
void self_cancel_callback() {
69+
emscripten_cancel_main_loop();
70+
};
71+
72+
73+
void export_main_loop_callbacks(py::module_& pyjs_module)
74+
{
75+
76+
77+
78+
// class for loop context
79+
py::class_<LoopContext>(pyjs_module, "LoopContext")
80+
.def(py::init<py::object, bool>(), py::arg("callback"), py::arg("cancel_loop_on_error") = true)
81+
.def_readwrite("exit_loop", &LoopContext::m_exit_loop)
82+
;
83+
84+
85+
86+
87+
88+
// Export main loop callbacks
89+
pyjs_module.def("_set_main_loop_callback", [](py::handle callback, int fps) {
90+
91+
// get a PyObject * from the handle
92+
auto py_object = callback.ptr();
93+
94+
// convert the PyObject to void*
95+
void* callback_ptr = reinterpret_cast<void*>(py_object);
96+
97+
98+
// use emscripten_set_main_loop_arg
99+
emscripten_set_main_loop_arg(
100+
wrapped_callback,
101+
callback_ptr, // pass the callback pointer as argument
102+
fps, // frames per second
103+
false
104+
);
105+
});
106+
107+
// explicit cancel main loop
108+
pyjs_module.def("_cancel_main_loop", []() {
109+
// This will cancel the main loop if it is currently running
110+
emscripten_cancel_main_loop();
111+
});
112+
113+
pyjs_module.def("_set_noop_main_loop", []() {
114+
// This will set a no-op main loop
115+
emscripten_set_main_loop(noop_callback, 1, false); // set a no-op loop to avoid errors
116+
});
117+
118+
119+
// pyjs_module.def("set_main_loop_callback_hl", [](py::handle & callback, int fps) {
120+
121+
// // get a PyObject * from the handle
122+
// void * handle_ptr = reinterpret_cast<void*>(&callback);
123+
124+
125+
126+
// // create a lambda without any caputres taking the callback pointer
127+
// auto wrapped_callback = [](void* cb_ptr) {
128+
129+
130+
// py::handle* handle = reinterpret_cast<py::handle*>(cb_ptr);
131+
132+
133+
// // We can use PyObject_CallObject to call the Python function
134+
135+
// };
136+
137+
// // use emscripten_set_main_loop_arg
138+
// emscripten_set_main_loop_arg(
139+
// wrapped_callback,
140+
// callback_ptr, // pass the callback pointer as argument
141+
// fps, // frames per second
142+
// false
143+
// );
144+
// });
145+
146+
147+
148+
}
149+
150+
151+
152+
void export_pyjs_module(py::module_& pyjs_module)
153+
{
154+
export_js_proxy(pyjs_module);
155+
export_main_loop_callbacks(pyjs_module);
38156

39157
}
40158
}

0 commit comments

Comments
 (0)