Skip to content

Commit

Permalink
Add godot.requestAnimationFrame and godot.cancelAnimationFrame
Browse files Browse the repository at this point in the history
  • Loading branch information
Geequlim committed Dec 22, 2019
1 parent ae558f2 commit 8cb0b19
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
1 change: 1 addition & 0 deletions ecmascript_binder.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ECMAScriptBinder {
protected:
// Path ==> ECMA Class
HashMap<String, ECMAClassInfo> ecma_classes;
HashMap<int64_t, ECMAScriptGCHandler> frame_callbacks;
static String BINDING_SCRIPT_CONTENT;

public:
Expand Down
19 changes: 19 additions & 0 deletions misc/godot.builtin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ declare module godot {
*/
function yield(target: godot.Object, signal: string): Promise<any[]>;

/**
* A long integer value, the request id, that uniquely identifies the entry in the callback list.
* This is a non-zero value, but you may not make any other assumptions about its value.
* You can pass this value to `godot.cancelAnimationFrame()` to cancel the refresh callback request.
*/
type FrameRequetID = number;

/**
* Request a refresh callback request, the `callback` will be called every frame
* @param callback The function to call when it's time to update your animation for the next repaint. The callback function is passed one single argument, a number similar to the one returned by `godot.OS.get_system_time_msecs()`, indicating the point in time when requestAnimationFrame() starts to execute callback functions.
*/
function requestAnimationFrame(callback: (time_stamp: number) => void): FrameRequetID;

/**
* Cancel an frame request previously scheduled through a call to `godot.requestAnimationFrame()`.
* @param request_id The ID value returned by the call to `godot.requestAnimationFrame()` that requested the callback.
*/
function cancelAnimationFrame(request_id: FrameRequetID): void;

const E: 2.7182818284590452353602874714;
const LN2: 0.6931471805599453094172321215;
const SQRT2: 1.4142135623730950488016887242;
Expand Down
49 changes: 49 additions & 0 deletions quickjs/quickjs_binder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "core/io/json.h"
#include "core/math/expression.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "quickjs_binder.h"

HashMap<JSContext *, QuickJSBinder *, QuickJSBinder::PtrHasher> QuickJSBinder::context_binders;
Expand Down Expand Up @@ -764,6 +765,12 @@ void QuickJSBinder::add_godot_globals() {
// godot.load
JSValue js_func_load = JS_NewCFunction(ctx, godot_load, "load", 1);
JS_DefinePropertyValueStr(ctx, godot_object, "load", js_func_load, PROP_DEF_DEFAULT);
// godot.requestAnimationFrame
JSValue js_func_requestAnimationFrame = JS_NewCFunction(ctx, godot_request_animation_frame, "requestAnimationFrame", 1);
JS_DefinePropertyValueStr(ctx, godot_object, "requestAnimationFrame", js_func_requestAnimationFrame, PROP_DEF_DEFAULT);
// godot.cancelAnimationFrame
JSValue js_func_cancelAnimationFrame = JS_NewCFunction(ctx, godot_cancel_animation_frame, "cancelAnimationFrame", 1);
JS_DefinePropertyValueStr(ctx, godot_object, "cancelAnimationFrame", js_func_cancelAnimationFrame, PROP_DEF_DEFAULT);

{
// godot.DEBUG_ENABLED
Expand Down Expand Up @@ -846,6 +853,16 @@ void QuickJSBinder::uninitialize() {
}
ecma_classes.clear();

// Free frame callbacks
const int64_t *id = frame_callbacks.next(NULL);
while (id) {
const ECMAScriptGCHandler &func = frame_callbacks.get(*id);
JSValueConst js_func = JS_MKPTR(JS_TAG_OBJECT, func.ecma_object);
JS_FreeValue(ctx, js_func);
id = frame_callbacks.next(id);
}
frame_callbacks.clear();

// modules
#if MODULE_HAS_REFCOUNT
const String *file = module_cache.next(NULL);
Expand Down Expand Up @@ -891,6 +908,15 @@ void QuickJSBinder::frame() {
break;
}
}

const int64_t *id = frame_callbacks.next(NULL);
while (id) {
const ECMAScriptGCHandler &func = frame_callbacks.get(*id);
JSValueConst js_func = JS_MKPTR(JS_TAG_OBJECT, func.ecma_object);
JSValue argvs = { JS_NewInt64(ctx, (int64_t)OS::get_singleton()->get_system_time_msecs()) };
JS_Call(ctx, js_func, godot_object, 1, &argvs);
id = frame_callbacks.next(id);
}
}

Error QuickJSBinder::eval_string(const String &p_source, const String &p_path) {
Expand Down Expand Up @@ -1243,6 +1269,29 @@ JSValue QuickJSBinder::godot_set_script_metadata(JSContext *ctx, JSValue this_va
return JS_UNDEFINED;
}

JSValue QuickJSBinder::godot_request_animation_frame(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
ERR_FAIL_COND_V(argc < 1 || !JS_IsFunction(ctx, argv[0]), JS_ThrowTypeError(ctx, "Function expected for argument #0"));
static int64_t id = 0;
JSValue js_func = JS_DupValue(ctx, argv[0]);
ECMAScriptGCHandler func;
func.ecma_object = JS_VALUE_GET_PTR(js_func);
QuickJSBinder *binder = get_context_binder(ctx);
binder->frame_callbacks.set(++id, func);
return JS_NewInt64(ctx, id);
}

JSValue QuickJSBinder::godot_cancel_animation_frame(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
ERR_FAIL_COND_V(argc < 1 || !JS_IsInteger(argv[0]), JS_ThrowTypeError(ctx, "Request ID expected for argument #0"));
int32_t id = js_to_int(ctx, argv[0]);
QuickJSBinder *binder = get_context_binder(ctx);
if (ECMAScriptGCHandler *callback = binder->frame_callbacks.getptr(id)) {
JSValue func = JS_MKPTR(JS_TAG_OBJECT, callback->ecma_object);
JS_FreeValue(ctx, func);
binder->frame_callbacks.erase(id);
}
return JS_UNDEFINED;
}

int QuickJSBinder::get_js_array_length(JSContext *ctx, JSValue p_val) {
if (!JS_IsArray(ctx, p_val)) return -1;
JSValue ret = JS_GetProperty(ctx, p_val, JS_ATOM_length);
Expand Down
2 changes: 2 additions & 0 deletions quickjs/quickjs_binder.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class QuickJSBinder : public ECMAScriptBinder {
static JSValue godot_register_signal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
static JSValue godot_register_property(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
static JSValue godot_set_script_metadata(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
static JSValue godot_request_animation_frame(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
static JSValue godot_cancel_animation_frame(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);

_FORCE_INLINE_ static JSValue js_empty_func(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_UNDEFINED; }
_FORCE_INLINE_ static JSValue js_empty_consturctor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { return JS_NewObject(ctx); }
Expand Down

0 comments on commit 8cb0b19

Please sign in to comment.