2023-05-23 15:45:10 +00:00
|
|
|
#include <stdbool.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <errno.h>
|
2023-05-04 15:00:22 +00:00
|
|
|
#include <quickjs.h>
|
2023-05-23 15:45:10 +00:00
|
|
|
//#include <quickjs-libc.h>
|
2023-05-26 11:28:53 +00:00
|
|
|
#include <GLFW/glfw3.h>
|
2023-05-04 15:00:22 +00:00
|
|
|
#include <raylib.h>
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
|
|
|
|
static JSContext *JS_NewCustomContext(JSRuntime *rt);
|
|
|
|
static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
|
|
|
|
const char *filename, int eval_flags);
|
|
|
|
|
|
|
|
static JSRuntime* rt;
|
|
|
|
static JSContext* ctx;
|
2023-05-26 11:28:53 +00:00
|
|
|
static bool shouldReload = true;
|
|
|
|
static const char* originalCwd = NULL;
|
2023-05-04 15:00:22 +00:00
|
|
|
|
2023-05-23 15:45:10 +00:00
|
|
|
static void pstrcpy(char *buf, int buf_size, const char *str)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
char *q = buf;
|
|
|
|
|
|
|
|
if (buf_size <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for(;;) {
|
|
|
|
c = *str++;
|
|
|
|
if (c == 0 || q >= buf + buf_size - 1)
|
|
|
|
break;
|
|
|
|
*q++ = c;
|
|
|
|
}
|
|
|
|
*q = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* strcat and truncate. */
|
|
|
|
static char *pstrcat(char *buf, int buf_size, const char *s)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
len = strlen(buf);
|
|
|
|
if (len < buf_size)
|
|
|
|
pstrcpy(buf + len, buf_size - len, s);
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val)
|
|
|
|
{
|
|
|
|
const char *str;
|
|
|
|
|
|
|
|
str = JS_ToCString(ctx, val);
|
|
|
|
if (str) {
|
|
|
|
fprintf(f, "%s\n", str);
|
|
|
|
JS_FreeCString(ctx, str);
|
|
|
|
} else {
|
|
|
|
fprintf(f, "[exception]\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val)
|
|
|
|
{
|
|
|
|
JSValue val;
|
|
|
|
bool is_error;
|
|
|
|
|
|
|
|
is_error = JS_IsError(ctx, exception_val);
|
|
|
|
js_dump_obj(ctx, stderr, exception_val);
|
|
|
|
if (is_error) {
|
|
|
|
val = JS_GetPropertyStr(ctx, exception_val, "stack");
|
|
|
|
if (!JS_IsUndefined(val)) {
|
|
|
|
js_dump_obj(ctx, stderr, val);
|
|
|
|
}
|
|
|
|
JS_FreeValue(ctx, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void js_std_dump_error(JSContext *ctx)
|
|
|
|
{
|
|
|
|
JSValue exception_val;
|
|
|
|
|
|
|
|
exception_val = JS_GetException(ctx);
|
|
|
|
js_std_dump_error1(ctx, exception_val);
|
|
|
|
JS_FreeValue(ctx, exception_val);
|
|
|
|
}
|
|
|
|
|
2023-05-10 21:26:53 +00:00
|
|
|
int app_update_quickjs(){
|
|
|
|
JSContext *ctx1;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* execute the pending jobs */
|
|
|
|
for(;;) {
|
|
|
|
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
|
|
|
|
if (err <= 0) {
|
|
|
|
if (err < 0) {
|
|
|
|
js_std_dump_error(ctx1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-05-26 11:28:53 +00:00
|
|
|
if(IsKeyPressed(KEY_F5)){
|
|
|
|
GLFWwindow* window = glfwGetCurrentContext();
|
|
|
|
glfwSetWindowShouldClose(window, GLFW_TRUE);
|
|
|
|
shouldReload = true;
|
|
|
|
}
|
|
|
|
|
2023-05-10 21:26:53 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-05-23 15:45:10 +00:00
|
|
|
static int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
|
|
|
|
JS_BOOL use_realpath, JS_BOOL is_main)
|
|
|
|
{
|
|
|
|
JSModuleDef *m;
|
|
|
|
char buf[1024 + 16];
|
|
|
|
JSValue meta_obj;
|
|
|
|
JSAtom module_name_atom;
|
|
|
|
const char *module_name;
|
|
|
|
|
|
|
|
assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE);
|
|
|
|
m = JS_VALUE_GET_PTR(func_val);
|
|
|
|
|
|
|
|
module_name_atom = JS_GetModuleName(ctx, m);
|
|
|
|
module_name = JS_AtomToCString(ctx, module_name_atom);
|
|
|
|
JS_FreeAtom(ctx, module_name_atom);
|
|
|
|
if (!module_name)
|
|
|
|
return -1;
|
|
|
|
if (!strchr(module_name, ':')) {
|
|
|
|
strcpy(buf, "file://");
|
|
|
|
#if !defined(_WIN32)
|
|
|
|
/* realpath() cannot be used with modules compiled with qjsc
|
|
|
|
because the corresponding module source code is not
|
|
|
|
necessarily present */
|
|
|
|
if (use_realpath) {
|
|
|
|
char *res = realpath(module_name, buf + strlen(buf));
|
|
|
|
if (!res) {
|
|
|
|
JS_ThrowTypeError(ctx, "realpath failure");
|
|
|
|
JS_FreeCString(ctx, module_name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
pstrcat(buf, sizeof(buf), module_name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pstrcpy(buf, sizeof(buf), module_name);
|
|
|
|
}
|
|
|
|
JS_FreeCString(ctx, module_name);
|
|
|
|
|
|
|
|
meta_obj = JS_GetImportMeta(ctx, m);
|
|
|
|
if (JS_IsException(meta_obj))
|
|
|
|
return -1;
|
|
|
|
JS_DefinePropertyValueStr(ctx, meta_obj, "url",
|
|
|
|
JS_NewString(ctx, buf),
|
|
|
|
JS_PROP_C_W_E);
|
|
|
|
JS_DefinePropertyValueStr(ctx, meta_obj, "main",
|
|
|
|
JS_NewBool(ctx, is_main),
|
|
|
|
JS_PROP_C_W_E);
|
|
|
|
JS_FreeValue(ctx, meta_obj);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename)
|
|
|
|
{
|
|
|
|
FILE *f;
|
|
|
|
uint8_t *buf;
|
|
|
|
size_t buf_len;
|
|
|
|
long lret;
|
|
|
|
|
|
|
|
f = fopen(filename, "rb");
|
|
|
|
if (!f)
|
|
|
|
return NULL;
|
|
|
|
if (fseek(f, 0, SEEK_END) < 0)
|
|
|
|
goto fail;
|
|
|
|
lret = ftell(f);
|
|
|
|
if (lret < 0)
|
|
|
|
goto fail;
|
|
|
|
/* XXX: on Linux, ftell() return LONG_MAX for directories */
|
|
|
|
if (lret == LONG_MAX) {
|
|
|
|
errno = EISDIR;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
buf_len = lret;
|
|
|
|
if (fseek(f, 0, SEEK_SET) < 0)
|
|
|
|
goto fail;
|
|
|
|
if (ctx)
|
|
|
|
buf = js_malloc(ctx, buf_len + 1);
|
|
|
|
else
|
|
|
|
buf = malloc(buf_len + 1);
|
|
|
|
if (!buf)
|
|
|
|
goto fail;
|
|
|
|
if (fread(buf, 1, buf_len, f) != buf_len) {
|
|
|
|
errno = EIO;
|
|
|
|
if (ctx)
|
|
|
|
js_free(ctx, buf);
|
|
|
|
else
|
|
|
|
free(buf);
|
|
|
|
fail:
|
|
|
|
fclose(f);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
buf[buf_len] = '\0';
|
|
|
|
fclose(f);
|
|
|
|
*pbuf_len = buf_len;
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2023-05-20 19:34:27 +00:00
|
|
|
void SetModelMaterial(Model *model, int materialIndex, Material material)
|
|
|
|
{
|
|
|
|
if(model->materialCount <= materialIndex) return;
|
|
|
|
UnloadMaterial(model->materials[materialIndex]);
|
|
|
|
model->materials[materialIndex] = material;
|
|
|
|
}
|
|
|
|
|
2023-05-10 21:26:53 +00:00
|
|
|
#include "bindings/js_raylib_core.h"
|
|
|
|
|
2023-05-23 15:45:10 +00:00
|
|
|
JSModuleDef *js_module_loader(JSContext *ctx,
|
|
|
|
const char *module_name, void *opaque)
|
|
|
|
{
|
|
|
|
JSModuleDef *m;
|
|
|
|
|
|
|
|
size_t buf_len;
|
|
|
|
uint8_t *buf;
|
|
|
|
JSValue func_val;
|
|
|
|
|
|
|
|
buf = js_load_file(ctx, &buf_len, module_name);
|
|
|
|
if (!buf) {
|
|
|
|
JS_ThrowReferenceError(ctx, "could not load module filename '%s'", module_name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* compile the module */
|
|
|
|
func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
|
|
|
|
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
|
|
js_free(ctx, buf);
|
|
|
|
if (JS_IsException(func_val))
|
|
|
|
return NULL;
|
|
|
|
/* XXX: could propagate the exception */
|
|
|
|
js_module_set_import_meta(ctx, func_val, true, false);
|
|
|
|
/* the module is already referenced, so we must free it */
|
|
|
|
m = JS_VALUE_GET_PTR(func_val);
|
|
|
|
JS_FreeValue(ctx, func_val);
|
|
|
|
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
2023-05-26 11:28:53 +00:00
|
|
|
static int js_run(int argc, char** argv){
|
2023-05-21 20:32:50 +00:00
|
|
|
TraceLog(LOG_INFO, "Starting QuickJS");
|
2023-05-04 15:00:22 +00:00
|
|
|
rt = JS_NewRuntime();
|
|
|
|
if (!rt)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "qjs: cannot allocate JS runtime\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2023-05-23 15:45:10 +00:00
|
|
|
//js_std_set_worker_new_context_func(JS_NewCustomContext);
|
|
|
|
//js_std_init_handlers(rt);
|
2023-05-04 15:00:22 +00:00
|
|
|
ctx = JS_NewCustomContext(rt);
|
|
|
|
if (!ctx) {
|
|
|
|
fprintf(stderr, "qjs: cannot allocate JS context\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
|
|
|
|
|
2023-05-23 15:45:10 +00:00
|
|
|
//js_std_add_helpers(ctx, argc, argv);
|
2023-05-04 15:00:22 +00:00
|
|
|
|
2023-05-14 20:19:47 +00:00
|
|
|
const char *str = "import * as rl from 'raylib'\n"
|
|
|
|
"for (const key in rl) { globalThis[key] = rl[key] }\n";
|
|
|
|
|
2023-05-04 15:00:22 +00:00
|
|
|
// const char *str = "import * as std from 'std';\n"
|
|
|
|
// "import * as os from 'os';\n"
|
|
|
|
// "globalThis.std = std;\n"
|
|
|
|
// "globalThis.os = os;\n";
|
2023-05-14 20:19:47 +00:00
|
|
|
eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
|
2023-05-04 15:00:22 +00:00
|
|
|
|
2023-05-21 20:32:50 +00:00
|
|
|
const char *buf;
|
|
|
|
if(argc <= 1){
|
|
|
|
const char *exePath = GetDirectoryPath(argv[0]);
|
|
|
|
TraceLog(LOG_INFO, "No parameters, looking for '%s/main.js'", exePath);
|
|
|
|
ChangeDirectory(exePath);
|
|
|
|
buf = LoadFileText("main.js");
|
|
|
|
} else if(argc > 1) {
|
|
|
|
// parameter is directory
|
|
|
|
if(DirectoryExists(argv[1])){
|
|
|
|
ChangeDirectory(argv[1]);
|
|
|
|
TraceLog(LOG_INFO, "Parameter is directory, looking for '%s/main.js'", argv[1]);
|
|
|
|
buf = LoadFileText("main.js");
|
|
|
|
// parameter is file
|
|
|
|
} else {
|
|
|
|
TraceLog(LOG_INFO, "Parameter is file, loading '%s'", argv[1]);
|
|
|
|
buf = LoadFileText(argv[1]);
|
|
|
|
ChangeDirectory(GetDirectoryPath(argv[1]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TraceLog(LOG_INFO, "Working directory is %s", GetWorkingDirectory());
|
2023-05-04 15:00:22 +00:00
|
|
|
if (!buf) {
|
2023-05-21 20:32:50 +00:00
|
|
|
JS_ThrowReferenceError(ctx, "could not find main module '%s'",argc > 1 ? argv[0] : "main.js");
|
2023-05-04 15:00:22 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2023-05-10 21:26:53 +00:00
|
|
|
size_t len = strlen(buf);
|
2023-05-04 15:00:22 +00:00
|
|
|
int res = eval_buf(ctx, buf, len, "main", JS_EVAL_TYPE_MODULE);
|
|
|
|
|
|
|
|
JS_FreeContext(ctx);
|
|
|
|
JS_FreeRuntime(rt);
|
2023-05-26 11:28:53 +00:00
|
|
|
|
2023-05-04 15:00:22 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-05-26 11:28:53 +00:00
|
|
|
int app_run_quickjs(int argc, char** argv){
|
|
|
|
const char* original = GetWorkingDirectory();
|
|
|
|
char* copy = calloc(strlen(original) + 1, sizeof(char));
|
|
|
|
strcpy(copy, original);
|
|
|
|
originalCwd = copy;
|
|
|
|
while(shouldReload){
|
|
|
|
shouldReload = false;
|
|
|
|
ChangeDirectory(originalCwd);
|
|
|
|
js_run(argc, argv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-04 15:00:22 +00:00
|
|
|
/* also used to initialize the worker context */
|
|
|
|
static JSContext *JS_NewCustomContext(JSRuntime *rt)
|
|
|
|
{
|
|
|
|
JSContext *ctx;
|
|
|
|
ctx = JS_NewContext(rt);
|
|
|
|
if (!ctx)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* system modules */
|
2023-05-23 15:45:10 +00:00
|
|
|
//js_init_module_std(ctx, "std");
|
2023-05-04 21:54:03 +00:00
|
|
|
//js_init_module_os(ctx, "os");
|
2023-05-14 20:19:47 +00:00
|
|
|
js_init_module_raylib_core(ctx, "raylib");
|
2023-05-04 15:00:22 +00:00
|
|
|
return ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
|
|
|
|
const char *filename, int eval_flags)
|
|
|
|
{
|
|
|
|
JSValue val;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
|
|
|
|
/* for the modules, we compile then run to be able to set
|
|
|
|
import.meta */
|
|
|
|
val = JS_Eval(ctx, (const char*)buf, buf_len, filename,
|
|
|
|
eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
|
|
if (!JS_IsException(val)) {
|
|
|
|
js_module_set_import_meta(ctx, val, 1, 1);
|
|
|
|
val = JS_EvalFunction(ctx, val);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val = JS_Eval(ctx, (const char*)buf, buf_len, filename, eval_flags);
|
|
|
|
}
|
|
|
|
if (JS_IsException(val)) {
|
|
|
|
js_std_dump_error(ctx);
|
|
|
|
ret = -1;
|
|
|
|
} else {
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
JS_FreeValue(ctx, val);
|
|
|
|
return ret;
|
|
|
|
}
|