Skip to content

Commit

Permalink
Continue event loop when event.preventDefault() is called for "erro…
Browse files Browse the repository at this point in the history
…r" or "unhandledrejection"
  • Loading branch information
TooTallNate committed Oct 21, 2023
1 parent 4422504 commit 99ccc71
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .changeset/nervous-keys-kick.md
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Continue event loop when `event.preventDefault()` is called for "error" or "unhandledrejection"
4 changes: 2 additions & 2 deletions packages/runtime/src/$.ts
Expand Up @@ -7,9 +7,9 @@ export interface Init {
batteryExit(): void;

// error.c
onError(fn: (err: any) => void): void;
onError(fn: (err: any) => number): void;
onUnhandledRejection(
fn: (promise: Promise<unknown>, reason: any) => void
fn: (promise: Promise<unknown>, reason: any) => number
): void;

// wasm.c
Expand Down
16 changes: 8 additions & 8 deletions packages/runtime/src/index.ts
Expand Up @@ -137,10 +137,10 @@ $.onError((e) => {
error: e,
});
dispatchEvent(ev);
if (!ev.defaultPrevented) {
console.error('Uncaught', e);
console.log('\nPress + to exit');
}
if (ev.defaultPrevented) return 0;
console.error('Uncaught', e);
console.log('\nPress + to exit');
return 1;
});

$.onUnhandledRejection((p, r) => {
Expand All @@ -149,10 +149,10 @@ $.onUnhandledRejection((p, r) => {
reason: r,
});
dispatchEvent(ev);
if (!ev.defaultPrevented) {
console.error('Uncaught (in promise)', r);
console.log('\nPress + to exit');
}
if (ev.defaultPrevented) return 0;
console.error('Uncaught (in promise)', r);
console.log('\nPress + to exit');
return 1;
});

Switch.addEventListener('frame', (event) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/timers.ts
Expand Up @@ -84,12 +84,12 @@ export function processTimers() {
const now = Date.now();
for (const [id, timer] of timers) {
if (now >= timer.invokeAt) {
timer.callback.apply(null, timer.args);
if (typeof timer.interval === 'number') {
timer.invokeAt = now + timer.interval;
} else {
clearTimeout(id);
}
timer.callback.apply(null, timer.args);
}
}
}
6 changes: 5 additions & 1 deletion source/async.c
Expand Up @@ -21,7 +21,7 @@ void nx_process_async(JSContext *ctx, nx_context_t *nx_ctx)
JS_FreeValue(ctx, cur->js_callback);
if (JS_IsException(ret_val))
{
print_js_error(ctx);
nx_emit_error_event(ctx);
}
JS_FreeValue(ctx, ret_val);
if (cur->data)
Expand All @@ -40,6 +40,10 @@ void nx_process_async(JSContext *ctx, nx_context_t *nx_ctx)
{
prev->next = cur;
}

// If the callback threw a fatal error
// then don't process any more async callbacks
if (nx_ctx->had_error) break;
}
else
{
Expand Down
25 changes: 8 additions & 17 deletions source/error.c
Expand Up @@ -4,40 +4,32 @@ void print_js_error(JSContext *ctx)
{
JSValue exception_val = JS_GetException(ctx);
const char *exception_str = JS_ToCString(ctx, exception_val);
printf("%s\n", exception_str);
fprintf(stderr, "%s\n", exception_str);
fflush(stderr);

printf("%s\n", exception_str);
JS_FreeCString(ctx, exception_str);

JSValue stack_val = JS_GetPropertyStr(ctx, exception_val, "stack");
const char *stack_str = JS_ToCString(ctx, stack_val);
printf("%s\n", stack_str);
fprintf(stderr, "%s\n", stack_str);
fflush(stderr);
printf("%s\n", stack_str);
JS_FreeCString(ctx, stack_str);

JS_FreeValue(ctx, exception_val);
JS_FreeValue(ctx, stack_val);
}

int nx_emit_error_event(JSContext *ctx)
void nx_emit_error_event(JSContext *ctx)
{
JSValue exception_val = JS_GetException(ctx);

nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
JSValueConst args[] = {exception_val};
JSValue ret_val = JS_Call(ctx, nx_ctx->onerror_handler, JS_NULL, 1, args);

// TODO: what should happen here?
// if (JS_IsException(ret_val))
//{
// print_js_error(ctx);
//}

JS_FreeValue(ctx, exception_val);
if (JS_IsException(ret_val))
print_js_error(ctx);
JS_ToInt32(ctx, &nx_ctx->had_error, ret_val);
JS_FreeValue(ctx, ret_val);

return 0;
}

static JSValue nx_set_onerror_handler(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
Expand All @@ -62,9 +54,8 @@ void nx_promise_rejection_handler(JSContext *ctx, JSValueConst promise,
JSValueConst args[] = {promise, reason};
JSValue ret_val = JS_Call(ctx, nx_ctx->unhandled_rejection_handler, JS_NULL, 2, args);
if (JS_IsException(ret_val))
{
print_js_error(ctx);
}
JS_ToInt32(ctx, &nx_ctx->had_error, ret_val);
JS_FreeValue(ctx, ret_val);
}

Expand Down
2 changes: 1 addition & 1 deletion source/error.h
Expand Up @@ -3,7 +3,7 @@

void print_js_error(JSContext *ctx);

int nx_emit_error_event(JSContext *ctx);
void nx_emit_error_event(JSContext *ctx);

void nx_promise_rejection_handler(JSContext *ctx, JSValueConst promise,
JSValueConst reason,
Expand Down
123 changes: 60 additions & 63 deletions source/main.c
Expand Up @@ -22,6 +22,8 @@
#include "tcp.h"
#include "poll.h"

#define LOG_FILENAME "nxjs-debug.log"

// Text renderer
static PrintConsole *print_console = NULL;

Expand All @@ -43,7 +45,7 @@ static JSValue js_console_exit(JSContext *ctx, JSValueConst this_val, int argc,
{
if (print_console != NULL)
{
consoleExit(NULL);
consoleExit(print_console);
print_console = NULL;
}
return JS_UNDEFINED;
Expand Down Expand Up @@ -413,7 +415,7 @@ void nx_process_pending_jobs(JSRuntime *rt)
{
if (err < 0)
{
print_js_error(ctx);
nx_emit_error_event(ctx);
}
break;
}
Expand Down Expand Up @@ -445,15 +447,15 @@ int main(int argc, char *argv[])
diagAbortWithResult(rc);
}

FILE *debug_fd = freopen(LOG_FILENAME, "w", stderr);

// Configure our supported input layout: a single player with standard controller styles
padConfigureInput(1, HidNpadStyleSet_NpadStandard);

// Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller)
PadState pad;
padInitializeDefault(&pad);

int had_error = 0;

JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);

Expand Down Expand Up @@ -501,8 +503,8 @@ int main(int argc, char *argv[])
{
free(js_path);
}
had_error = 1;
goto wait_error;
nx_ctx->had_error = 1;
goto main_loop;
}

size_t runtime_buffer_size;
Expand All @@ -511,21 +513,21 @@ int main(int argc, char *argv[])
if (runtime_buffer == NULL)
{
printf("%s: %s\n", strerror(errno), runtime_path);
had_error = 1;
goto wait_error;
nx_ctx->had_error = 1;
goto main_loop;
}

JSValue runtime_init_result = JS_Eval(ctx, runtime_buffer, runtime_buffer_size, runtime_path, JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(runtime_init_result))
{
print_js_error(ctx);
had_error = 1;
nx_ctx->had_error = 1;
}
JS_FreeValue(ctx, runtime_init_result);
free(runtime_buffer);
if (had_error)
if (nx_ctx->had_error)
{
goto wait_error;
goto main_loop;
}

JSValue switch_obj = JS_GetPropertyStr(ctx, global_obj, "Switch");
Expand Down Expand Up @@ -604,19 +606,15 @@ int main(int argc, char *argv[])
JSValue user_code_result = JS_Eval(ctx, user_code, user_code_size, js_path, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
if (JS_IsException(user_code_result))
{
// print_js_error(ctx);
nx_emit_error_event(ctx);
had_error = 1;
}
else
{
nx_module_set_import_meta(ctx, user_code_result, js_path, true);
user_code_result = JS_EvalFunction(ctx, user_code_result);
if (JS_IsException(user_code_result))
{
// print_js_error(ctx);
nx_emit_error_event(ctx);
had_error = 1;
}
}
JS_FreeValue(ctx, user_code_result);
Expand All @@ -625,60 +623,74 @@ int main(int argc, char *argv[])
{
free(js_path);
}
if (had_error)
{
goto wait_error;
}

// Main loop
main_loop:
while (appletMainLoop())
{
// Check if any file descriptors have reported activity
nx_poll(&nx_ctx->poll);
if (!nx_ctx->had_error)
{
// Check if any file descriptors have reported activity
nx_poll(&nx_ctx->poll);
}

// Check if any thread pool tasks have completed
nx_process_async(ctx, nx_ctx);
if (!nx_ctx->had_error)
nx_process_async(ctx, nx_ctx);

// Process any Promises that need to be fulfilled
nx_process_pending_jobs(rt);
if (!nx_ctx->had_error)
nx_process_pending_jobs(rt);

padUpdate(&pad);
u64 kDown = padGetButtons(&pad);

// Dispatch "frame" event
JSValue event_obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "frame"));
JS_SetPropertyStr(ctx, event_obj, "detail", JS_NewUint32(ctx, kDown));
JSValueConst args[] = {event_obj};
JSValue ret_val = JS_Call(ctx, dispatch_event_func, switch_obj, 1, args);
JS_FreeValue(ctx, event_obj);

if (!is_running)
if (nx_ctx->had_error)
{
if (kDown & HidNpadButton_Plus)
{
// When an initialization or unhandled error occurs,
// wait until the user presses "+" to fully exit so
// the user has a chance to read the error message.
break;
}
}
else
{
// `Switch.exit()` was called
// Dispatch "frame" event
JSValue event_obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "frame"));
JS_SetPropertyStr(ctx, event_obj, "detail", JS_NewUint32(ctx, kDown));
JSValueConst args[] = {event_obj};
JSValue ret_val = JS_Call(ctx, dispatch_event_func, switch_obj, 1, args);
JS_FreeValue(ctx, event_obj);

if (!is_running)
{
// `Switch.exit()` was called
JS_FreeValue(ctx, ret_val);
break;
}

if (JS_IsException(ret_val))
{
nx_emit_error_event(ctx);
}
JS_FreeValue(ctx, ret_val);
break;
}

if (JS_IsException(ret_val))
if (print_console != NULL)
{
print_js_error(ctx);
// Update the console, sending a new frame to the display
consoleUpdate(print_console);
}
JS_FreeValue(ctx, ret_val);

if (framebuffer != NULL)
else if (framebuffer != NULL)
{
// Copy the JS framebuffer to the current Switch buffer
u32 stride;
u8 *framebuf = (u8 *)framebufferBegin(framebuffer, &stride);
memcpy(framebuf, js_framebuffer, 1280 * 720 * 4);
framebufferEnd(framebuffer);
}
else if (print_console != NULL)
{
// Update the console, sending a new frame to the display
consoleUpdate(print_console);
}
}

// XXX: Ideally we wouldn't `thpool_wait()` here,
Expand All @@ -695,23 +707,8 @@ int main(int argc, char *argv[])
JS_FreeValue(ctx, event_obj);
JS_FreeValue(ctx, ret_val);

wait_error:
if (had_error)
{
// When an initialization or unhandled error occurs,
// wait until the user presses "+" to fully exit so
// the user has a chance to read the error message.
while (appletMainLoop())
{
padUpdate(&pad);
u64 kDown = padGetButtonsDown(&pad);
if (kDown & HidNpadButton_Plus)
break;
consoleUpdate(NULL);
}
}

FILE *leaks_fd = freopen("leaks.txt", "w", stdout);
fclose(debug_fd);
FILE *leaks_fd = freopen(LOG_FILENAME, "a", stdout);

JS_FreeValue(ctx, dispatch_event_func);
JS_FreeValue(ctx, native_obj);
Expand Down Expand Up @@ -742,7 +739,7 @@ int main(int argc, char *argv[])
fclose(leaks_fd);

/* If no leaks were detected then delete the log file */
delete_if_empty("leaks.txt");
delete_if_empty(LOG_FILENAME);

return 0;
}

0 comments on commit 99ccc71

Please sign in to comment.