From 2e986178c19529b2f5bb4735117913118749f0a4 Mon Sep 17 00:00:00 2001 From: Stjepan Rajko Date: Tue, 1 Mar 2016 13:53:08 -0700 Subject: [PATCH 1/6] Add AsyncBatonWithResult and ExecuteAsync just refactoring, eliminating duplicated code --- .../templates/manual/include/async_baton.h | 27 +++++++++++++++++++ .../templates/manual/include/lock_master.h | 2 ++ .../templates/partials/callback_helpers.cc | 17 +----------- .../templates/partials/field_accessors.cc | 17 +----------- generate/templates/templates/class_content.cc | 1 - generate/templates/templates/class_header.h | 4 +-- .../templates/templates/struct_content.cc | 1 - generate/templates/templates/struct_header.h | 4 +-- 8 files changed, 33 insertions(+), 40 deletions(-) diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index a1ce5c380..43833a2ad 100644 --- a/generate/templates/manual/include/async_baton.h +++ b/generate/templates/manual/include/async_baton.h @@ -4,6 +4,9 @@ #include #include +#include "lock_master.h" +#include "functions/sleep_for_ms.h" + // Base class for Batons used for callbacks (for example, // JS functions passed as callback parameters, // or field properties of configuration objects whose values are callbacks) @@ -13,4 +16,28 @@ struct AsyncBaton { bool done; }; +template +struct AsyncBatonWithResult : public AsyncBaton { + ResultT result; + + ResultT ExecuteAsync(uv_async_cb asyncCallback) { + result = 0; + req.data = this; + done = false; + + uv_async_init(uv_default_loop(), &req, asyncCallback); + { + LockMaster::TemporaryUnlock temporaryUnlock; + + uv_async_send(&req); + + while(!done) { + sleep_for_ms(1); + } + } + + return result; + } +}; + #endif diff --git a/generate/templates/manual/include/lock_master.h b/generate/templates/manual/include/lock_master.h index fd1a09b6f..7fd4ee8dc 100644 --- a/generate/templates/manual/include/lock_master.h +++ b/generate/templates/manual/include/lock_master.h @@ -1,6 +1,8 @@ #ifndef LOCK_MASTER_H #define LOCK_MASTER_H +#include + class LockMasterImpl; class LockMaster { diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index 0acd215ae..0a1d7e782 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -12,22 +12,7 @@ baton->{{ arg.name }} = {{ arg.name }}; {% endeach %} - baton->result = 0; - baton->req.data = baton; - baton->done = false; - - uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ cppFunctionName }}_{{ cbFunction.name }}_async); - { - LockMaster::TemporaryUnlock temporaryUnlock; - - uv_async_send(&baton->req); - - while(!baton->done) { - sleep_for_ms(1); - } - } - - return baton->result; + return baton->ExecuteAsync((uv_async_cb) {{ cppFunctionName }}_{{ cbFunction.name }}_async); } void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_async_t* req, int status) { diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 17b338dc3..1f64a9a0b 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -93,22 +93,7 @@ baton->{{ arg.name }} = {{ arg.name }}; {% endeach %} - baton->result = 0; - baton->req.data = baton; - baton->done = false; - - uv_async_init(uv_default_loop(), &baton->req, (uv_async_cb) {{ field.name }}_async); - { - LockMaster::TemporaryUnlock temporaryUnlock; - - uv_async_send(&baton->req); - - while(!baton->done) { - sleep_for_ms(1); - } - } - - return baton->result; + return baton->ExecuteAsync((uv_async_cb) {{ field.name }}_async); } void {{ cppClassName }}::{{ field.name }}_async(uv_async_t* req, int status) { diff --git a/generate/templates/templates/class_content.cc b/generate/templates/templates/class_content.cc index c172e1fa5..8fead5aa8 100644 --- a/generate/templates/templates/class_content.cc +++ b/generate/templates/templates/class_content.cc @@ -11,7 +11,6 @@ extern "C" { #include "../include/lock_master.h" #include "../include/functions/copy.h" #include "../include/{{ filename }}.h" -#include "../include/functions/sleep_for_ms.h" {% each dependencies as dependency %} #include "{{ dependency }}" diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 84c9b5c4f..1d493459b 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -64,12 +64,10 @@ class {{ cppClassName }} : public Nan::ObjectWrap { static void {{ function.cppFunctionName }}_{{ arg.name }}_async(uv_async_t* req, int status); static void {{ function.cppFunctionName }}_{{ arg.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); - struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : AsyncBaton { + struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : public AsyncBatonWithResult<{{ arg.return.type }}> { {% each arg.args|argsInfo as cbArg %} {{ cbArg.cType }} {{ cbArg.name }}; {% endeach %} - - {{ arg.return.type }} result; }; {% endif %} {% endeach %} diff --git a/generate/templates/templates/struct_content.cc b/generate/templates/templates/struct_content.cc index cab524a80..561d5c0ef 100644 --- a/generate/templates/templates/struct_content.cc +++ b/generate/templates/templates/struct_content.cc @@ -17,7 +17,6 @@ extern "C" { #include "../include/lock_master.h" #include "../include/functions/copy.h" #include "../include/{{ filename }}.h" -#include "../include/functions/sleep_for_ms.h" {% each dependencies as dependency %} #include "{{ dependency }}" diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 0320fbbf6..149f97647 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -48,12 +48,10 @@ class {{ cppClassName }} : public Nan::ObjectWrap { static void {{ field.name }}_async(uv_async_t* req, int status); static void {{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); - struct {{ field.name|titleCase }}Baton : public AsyncBaton { + struct {{ field.name|titleCase }}Baton : public AsyncBatonWithResult<{{ field.return.type }}> { {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}; {% endeach %} - - {{ field.return.type }} result; }; {% endif %} {% endif %} From 266aa3fa2f7be7c79bf76124919e9f40b9072ed0 Mon Sep 17 00:00:00 2001 From: Stjepan Rajko Date: Tue, 1 Mar 2016 14:28:17 -0700 Subject: [PATCH 2/6] Store defaultResult in baton I originally tried doing the throttling in the baton but that ended up being a mistake. This moved the default result to the baton so the baton could use it. I don't think the change is necessary for the final version of this PR, but I still like moving things out of combyne-templated files so I kept it. --- generate/templates/manual/include/async_baton.h | 5 +++++ generate/templates/partials/callback_helpers.cc | 11 ++++++----- generate/templates/partials/field_accessors.cc | 13 +++++++------ generate/templates/templates/class_header.h | 4 ++++ generate/templates/templates/struct_header.h | 4 ++++ 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index 43833a2ad..fee87c4c1 100644 --- a/generate/templates/manual/include/async_baton.h +++ b/generate/templates/manual/include/async_baton.h @@ -19,6 +19,11 @@ struct AsyncBaton { template struct AsyncBatonWithResult : public AsyncBaton { ResultT result; + ResultT defaultResult; // result returned if the callback doesn't return anything valid + + AsyncBatonWithResult(const ResultT &defaultResult) + : defaultResult(defaultResult) { + } ResultT ExecuteAsync(uv_async_cb asyncCallback) { result = 0; diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index 0a1d7e782..b776c0097 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -6,7 +6,8 @@ {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} {% endeach %} ) { - {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = new {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton(); + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = + new {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton({{ cbFunction.return.noResults }}); {% each cbFunction.args|argsInfo as arg %} baton->{{ arg.name }} = {{ arg.name }}; @@ -78,12 +79,12 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(uv_as baton->result = (int)result->ToNumber()->Value(); } else { - baton->result = {{ cbFunction.return.noResults }}; + baton->result = baton->defaultResult; } {% endif %} } else { - baton->result = {{ cbFunction.return.noResults }}; + baton->result = baton->defaultResult; } {% endeach %} @@ -112,12 +113,12 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_promiseComp baton->result = (int)result->ToNumber()->Value(); } else { - baton->result = {{ cbFunction.return.noResults }}; + baton->result = baton->defaultResult; } {% endif %} } else { - baton->result = {{ cbFunction.return.noResults }}; + baton->result = baton->defaultResult; } {% endeach %} } diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 1f64a9a0b..6081f15fd 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -87,7 +87,8 @@ {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} {% endeach %} ) { - {{ field.name|titleCase }}Baton* baton = new {{ field.name|titleCase }}Baton(); + {{ field.name|titleCase }}Baton* baton = + new {{ field.name|titleCase }}Baton({{ field.return.noResults }}); {% each field.args|argsInfo as arg %} baton->{{ arg.name }} = {{ arg.name }}; @@ -106,7 +107,7 @@ if (instance->{{ field.name }}->IsEmpty()) { {% if field.return.type == "int" %} - baton->result = {{ field.return.noResults }}; // no results acquired + baton->result = baton->defaultResult; // no results acquired {% endif %} baton->done = true; @@ -172,12 +173,12 @@ baton->result = (int)result->ToNumber()->Value(); } else { - baton->result = {{ field.return.noResults }}; + baton->result = baton->defaultResult; } {% endif %} } else { - baton->result = {{ field.return.noResults }}; + baton->result = baton->defaultResult; } {% endeach %} baton->done = true; @@ -205,12 +206,12 @@ baton->result = (int)result->ToNumber()->Value(); } else{ - baton->result = {{ field.return.noResults }}; + baton->result = baton->defaultResult; } {% endif %} } else { - baton->result = {{ field.return.noResults }}; + baton->result = baton->defaultResult; } {% endeach %} } diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 1d493459b..b18defd0e 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -68,6 +68,10 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {% each arg.args|argsInfo as cbArg %} {{ cbArg.cType }} {{ cbArg.name }}; {% endeach %} + + {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton(const {{ arg.return.type }} &defaultResult) + : AsyncBatonWithResult<{{ arg.return.type }}>(defaultResult) { + } }; {% endif %} {% endeach %} diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 149f97647..703023556 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -52,6 +52,10 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}; {% endeach %} + + {{ field.name|titleCase }}Baton(const {{ field.return.type }} &defaultResult) + : AsyncBatonWithResult<{{ field.return.type }}>(defaultResult) { + } }; {% endif %} {% endif %} From 89faca2026a707024ef606a5eefac6f74ff0c27a Mon Sep 17 00:00:00 2001 From: Stjepan Rajko Date: Wed, 2 Mar 2016 15:28:43 -0700 Subject: [PATCH 3/6] Refactor structs to use CallbackWrapper for callbacks just refactoring, leverages the previously unused CallbackWrapper to set us up for bundling throttling state with the javascript callback. --- .../manual/include/callback_wrapper.h | 26 +++++++++++++++++-- .../templates/partials/field_accessors.cc | 14 ++++------ .../templates/templates/struct_content.cc | 4 --- generate/templates/templates/struct_header.h | 3 ++- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/generate/templates/manual/include/callback_wrapper.h b/generate/templates/manual/include/callback_wrapper.h index 41552de3e..9b848bbd6 100644 --- a/generate/templates/manual/include/callback_wrapper.h +++ b/generate/templates/manual/include/callback_wrapper.h @@ -9,9 +9,31 @@ using namespace v8; using namespace node; -struct CallbackWrapper { +class CallbackWrapper { Nan::Callback* jsCallback; - void * payload; + +public: + CallbackWrapper() { + jsCallback = NULL; + } + ~CallbackWrapper() { + SetCallback(NULL); + } + + bool HasCallback() { + return jsCallback != NULL; + } + + Nan::Callback* GetCallback() { + return jsCallback; + } + + void SetCallback(Nan::Callback* callback) { + if(jsCallback) { + delete jsCallback; + } + jsCallback = callback; + } }; #endif diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 6081f15fd..66a9c44fc 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -11,8 +11,8 @@ info.GetReturnValue().Set(Nan::New(wrapper->{{ field.name }})); {% elsif field.isCallbackFunction %} - if (wrapper->{{field.name}} != NULL) { - info.GetReturnValue().Set(wrapper->{{ field.name }}->GetFunction()); + if (wrapper->{{field.name}}.HasCallback()) { + info.GetReturnValue().Set(wrapper->{{ field.name }}.GetCallback()->GetFunction()); } else { info.GetReturnValue().SetUndefined(); } @@ -47,16 +47,12 @@ wrapper->raw->{{ field.name }} = {% if not field.cType | isPointer %}*{% endif %}{% if field.cppClassName == 'GitStrarray' %}StrArrayConverter::Convert({{ field.name }}->ToObject()){% else %}Nan::ObjectWrap::Unwrap<{{ field.cppClassName }}>({{ field.name }}->ToObject())->GetValue(){% endif %}; {% elsif field.isCallbackFunction %} - if (wrapper->{{ field.name }} != NULL) { - delete wrapper->{{ field.name }}; - } - if (value->IsFunction()) { if (!wrapper->raw->{{ field.name }}) { wrapper->raw->{{ field.name }} = ({{ field.cType }}){{ field.name }}_cppCallback; } - wrapper->{{ field.name }} = new Nan::Callback(value.As()); + wrapper->{{ field.name }}.SetCallback(new Nan::Callback(value.As())); } {% elsif field.payloadFor %} @@ -105,7 +101,7 @@ {% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %} {% endeach %}); - if (instance->{{ field.name }}->IsEmpty()) { + if (instance->{{ field.name }}.GetCallback()->IsEmpty()) { {% if field.return.type == "int" %} baton->result = baton->defaultResult; // no results acquired {% endif %} @@ -149,7 +145,7 @@ }; Nan::TryCatch tryCatch; - Local result = instance->{{ field.name }}->Call({{ field.args|jsArgsCount }}, argv); + Local result = instance->{{ field.name }}.GetCallback()->Call({{ field.args|jsArgsCount }}, argv); uv_close((uv_handle_t*) &baton->req, NULL); diff --git a/generate/templates/templates/struct_content.cc b/generate/templates/templates/struct_content.cc index 561d5c0ef..b54af5510 100644 --- a/generate/templates/templates/struct_content.cc +++ b/generate/templates/templates/struct_content.cc @@ -52,10 +52,7 @@ using namespace std; {% if not field.ignore %} {% if not field.isEnum %} {% if field.isCallbackFunction %} - if (this->{{ field.name }} != NULL) { - delete this->{{ field.name }}; this->raw->{{ fields|payloadFor field.name }} = NULL; - } {% endif %} {% endif %} {% endif %} @@ -83,7 +80,6 @@ void {{ cppClassName }}::ConstructFields() { // the current instance this->raw->{{ field.name }} = NULL; this->raw->{{ fields|payloadFor field.name }} = (void *)this; - this->{{ field.name }} = NULL; {% elsif field.payloadFor %} Local {{ field.name }} = Nan::Undefined(); diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 703023556..d0a797227 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -6,6 +6,7 @@ #include #include "async_baton.h" +#include "callback_wrapper.h" extern "C" { #include @@ -75,7 +76,7 @@ class {{ cppClassName }} : public Nan::ObjectWrap { {% if field.isLibgitType %} Nan::Persistent {{ field.name }}; {% elsif field.isCallbackFunction %} - Nan::Callback* {{ field.name }}; + CallbackWrapper {{ field.name }}; {% elsif field.payloadFor %} Nan::Persistent {{ field.name }}; {% endif %} From e8f8de5c9e18b1f623262689545a0157e2885d25 Mon Sep 17 00:00:00 2001 From: Stjepan Rajko Date: Wed, 2 Mar 2016 17:52:01 -0700 Subject: [PATCH 4/6] Add callback throttle functionality --- .../manual/include/callback_wrapper.h | 31 +++++++++++--- .../templates/partials/field_accessors.cc | 42 +++++++++++++++++-- generate/templates/templates/struct_header.h | 2 + 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/generate/templates/manual/include/callback_wrapper.h b/generate/templates/manual/include/callback_wrapper.h index 9b848bbd6..b23a7bb36 100644 --- a/generate/templates/manual/include/callback_wrapper.h +++ b/generate/templates/manual/include/callback_wrapper.h @@ -1,10 +1,8 @@ #ifndef CALLBACK_WRAPPER_H #define CALLBACK_WRAPPER_H -#include -#include - -#include "nan.h" +#include +#include using namespace v8; using namespace node; @@ -12,10 +10,17 @@ using namespace node; class CallbackWrapper { Nan::Callback* jsCallback; + // throttling data, used for callbacks that need to be throttled + int throttle; // in milliseconds - if > 0, calls to the JS callback will be throttled + uint64_t lastCallTime; + public: CallbackWrapper() { jsCallback = NULL; + lastCallTime = 0; + throttle = 0; } + ~CallbackWrapper() { SetCallback(NULL); } @@ -28,11 +33,27 @@ class CallbackWrapper { return jsCallback; } - void SetCallback(Nan::Callback* callback) { + void SetCallback(Nan::Callback* callback, int throttle = 0) { if(jsCallback) { delete jsCallback; } jsCallback = callback; + this->throttle = throttle; + } + + bool WillBeThrottled() { + if(!throttle) { + return false; + } + // throttle if needed + uint64_t now = uv_hrtime(); + if(lastCallTime > 0 && now < lastCallTime + throttle * 1000000) { + // throttled + return true; + } else { + lastCallTime = now; + return false; + } } }; diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 66a9c44fc..6a3123095 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -31,6 +31,7 @@ } NAN_SETTER({{ cppClassName }}::Set{{ field.cppFunctionName }}) { + Nan::HandleScope scope; {{ cppClassName }} *wrapper = Nan::ObjectWrap::Unwrap<{{ cppClassName }}>(info.This()); @@ -47,12 +48,35 @@ wrapper->raw->{{ field.name }} = {% if not field.cType | isPointer %}*{% endif %}{% if field.cppClassName == 'GitStrarray' %}StrArrayConverter::Convert({{ field.name }}->ToObject()){% else %}Nan::ObjectWrap::Unwrap<{{ field.cppClassName }}>({{ field.name }}->ToObject())->GetValue(){% endif %}; {% elsif field.isCallbackFunction %} + Nan::Callback *callback = NULL; + int throttle = {%if field.return.throttle %}{{ field.return.throttle }}{%else%}0{%endif%}; + if (value->IsFunction()) { + callback = new Nan::Callback(value.As()); + } else if (value->IsObject()) { + Local object = value.As(); + Local callbackKey; + Nan::MaybeLocal maybeObjectCallback = Nan::Get(object, Nan::New("callback").ToLocalChecked()); + if (!maybeObjectCallback.IsEmpty()) { + Local objectCallback = maybeObjectCallback.ToLocalChecked(); + if (objectCallback->IsFunction()) { + callback = new Nan::Callback(objectCallback.As()); + Nan::MaybeLocal maybeObjectThrottle = Nan::Get(object, Nan::New("throttle").ToLocalChecked()); + if(!maybeObjectThrottle.IsEmpty()) { + Local objectThrottle = maybeObjectThrottle.ToLocalChecked(); + if (objectThrottle->IsNumber()) { + throttle = (int)objectThrottle.As()->Value(); + } + } + } + } + } + if (callback) { if (!wrapper->raw->{{ field.name }}) { wrapper->raw->{{ field.name }} = ({{ field.cType }}){{ field.name }}_cppCallback; } - wrapper->{{ field.name }}.SetCallback(new Nan::Callback(value.As())); + wrapper->{{ field.name }}.SetCallback(callback, throttle); } {% elsif field.payloadFor %} @@ -78,6 +102,12 @@ } {% if field.isCallbackFunction %} + {{ cppClassName }}* {{ cppClassName }}::{{ field.name }}_getInstanceFromBaton({{ field.name|titleCase }}Baton* baton) { + return static_cast<{{ cppClassName }}*>(baton->{% each field.args|argsInfo as arg %} + {% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %} + {% endeach %}); + } + {{ field.return.type }} {{ cppClassName }}::{{ field.name }}_cppCallback ( {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name}}{% if not arg.lastArg %},{% endif %} @@ -90,6 +120,12 @@ baton->{{ arg.name }} = {{ arg.name }}; {% endeach %} + {{ cppClassName }}* instance = {{ field.name }}_getInstanceFromBaton(baton); + + if (instance->{{ field.name }}.WillBeThrottled()) { + return baton->defaultResult; + } + return baton->ExecuteAsync((uv_async_cb) {{ field.name }}_async); } @@ -97,9 +133,7 @@ Nan::HandleScope scope; {{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(req->data); - {{ cppClassName }}* instance = static_cast<{{ cppClassName }}*>(baton->{% each field.args|argsInfo as arg %} - {% if arg.payload == true %}{{arg.name}}{% elsif arg.lastArg %}{{arg.name}}{% endif %} - {% endeach %}); + {{ cppClassName }}* instance = {{ field.name }}_getInstanceFromBaton(baton); if (instance->{{ field.name }}.GetCallback()->IsEmpty()) { {% if field.return.type == "int" %} diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index d0a797227..87720aa4a 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -58,6 +58,8 @@ class {{ cppClassName }} : public Nan::ObjectWrap { : AsyncBatonWithResult<{{ field.return.type }}>(defaultResult) { } }; + static {{ cppClassName }} * {{ field.name }}_getInstanceFromBaton ( + {{ field.name|titleCase }}Baton *baton); {% endif %} {% endif %} {% endeach %} From f5286a89be443402f3652f1ec450da25fc2d594c Mon Sep 17 00:00:00 2001 From: Stjepan Rajko Date: Wed, 2 Mar 2016 17:53:23 -0700 Subject: [PATCH 5/6] Specify throttle in progress callback descriptors default settings for progress callbacks. --- generate/input/callbacks.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/generate/input/callbacks.json b/generate/input/callbacks.json index d18919768..eb6b128a3 100644 --- a/generate/input/callbacks.json +++ b/generate/input/callbacks.json @@ -96,7 +96,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "throttle": 100 } }, "git_checkout_perfdata_cb": { @@ -207,7 +208,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "throttle": 100 } }, "git_diff_hunk_cb": { @@ -560,7 +562,8 @@ "type": "int", "noResults":0, "success": 0, - "error": -1 + "error": -1, + "throttle": 100 } }, "git_stash_cb": { @@ -670,7 +673,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1 + "error": -1, + "throttle": 100 } }, "git_transport_cb": { From b6215ac57abec90a3ca6bfb5995916af9448c3d5 Mon Sep 17 00:00:00 2001 From: Stjepan Rajko Date: Wed, 2 Mar 2016 17:53:46 -0700 Subject: [PATCH 6/6] Add throttling tests --- test/tests/clone.js | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/test/tests/clone.js b/test/tests/clone.js index 46830a889..3e2a86446 100644 --- a/test/tests/clone.js +++ b/test/tests/clone.js @@ -3,6 +3,7 @@ var assert = require("assert"); var promisify = require("promisify-node"); var fse = promisify(require("fs-extra")); var local = path.join.bind(path, __dirname); +var _ = require("lodash"); describe("Clone", function() { var NodeGit = require("../../"); @@ -37,6 +38,114 @@ describe("Clone", function() { }); }); + it("can clone twice with http using same config object", function() { + var test = this; + var url = "http://git.tbranyen.com/smart/site-content"; + var progressCount = 0; + var opts = { + fetchOpts: { + callbacks: { + transferProgress: function(progress) { + progressCount++; + } + } + } + }; + + return Clone(url, clonePath, opts) + .then(function(repo) { + assert.ok(repo instanceof Repository); + assert.notEqual(progressCount, 0); + return fse.remove(clonePath); + }) + .then(function() { + progressCount = 0; + return Clone(url, clonePath, opts); + }) + .then(function(repo) { + assert.ok(repo instanceof Repository); + assert.notEqual(progressCount, 0); + test.repository = repo; + }); + }); + + function updateProgressIntervals(progressIntervals, lastInvocation) { + var now = new Date(); + if (lastInvocation) { + progressIntervals.push(now - lastInvocation); + } + return now; + } + + it("can clone with http and default throttled progress", function() { + var test = this; + var url = "http://git.tbranyen.com/smart/site-content"; + var progressCount = 0; + var lastInvocation; + var progressIntervals = []; + var opts = { + fetchOpts: { + callbacks: { + transferProgress: function(progress) { + lastInvocation = updateProgressIntervals(progressIntervals); + progressCount++; + } + } + } + }; + + return Clone(url, clonePath, opts).then(function(repo) { + assert.ok(repo instanceof Repository); + assert.notEqual(progressCount, 0); + var averageProgressInterval = _.sum(progressIntervals) / + progressIntervals.length; + // even though we are specifying a throttle period of 100, + // the throttle is applied on the scheduling side, + // and actual execution is at the mercy of the main js thread + // so the actual throttle intervals could be less than the specified + // throttle period + if (averageProgressInterval < 75) { + assert.fail(averageProgressInterval, 75, + "unexpected average time between callbacks", "<"); + } + test.repository = repo; + }); + }); + + it("can clone with http and explicitly throttled progress", function() { + var test = this; + var url = "http://git.tbranyen.com/smart/site-content"; + var progressCount = 0; + var lastInvocation; + var progressIntervals = []; + var opts = { + fetchOpts: { + callbacks: { + transferProgress: { + throttle: 50, + callback: function(progress) { + lastInvocation = updateProgressIntervals(progressIntervals, + lastInvocation); + progressCount++; + } + } + } + } + }; + + return Clone(url, clonePath, opts).then(function(repo) { + assert.ok(repo instanceof Repository); + assert.notEqual(progressCount, 0); + var averageProgressInterval = _.sum(progressIntervals) / + progressIntervals.length; + if (averageProgressInterval < 35) { + assert.fail(averageProgressInterval, 35, + "unexpected average time between callbacks", "<"); + } + test.repository = repo; + }); + }); + it("can clone with https", function() { var test = this; var url = "https://github.com/nodegit/test.git";