diff --git a/NativeScript/NativeScript.h b/NativeScript/NativeScript.h index f9bcc9f1..de5e40ee 100644 --- a/NativeScript/NativeScript.h +++ b/NativeScript/NativeScript.h @@ -26,3 +26,10 @@ - (bool)liveSync; @end + +@interface NativeScriptRuntime : NSObject + ++ (BOOL)reloadApplication; ++ (BOOL)reloadApplication:(NSString*)baseDir; + +@end diff --git a/NativeScript/NativeScript.mm b/NativeScript/NativeScript.mm index 11f8ae80..6ccdacef 100644 --- a/NativeScript/NativeScript.mm +++ b/NativeScript/NativeScript.mm @@ -24,6 +24,21 @@ @implementation Config @end +static Config* CopyConfig(Config* config) { + Config* copy = [[Config alloc] init]; + copy.BaseDir = config.BaseDir; + copy.ApplicationPath = config.ApplicationPath; + copy.MetadataPtr = config.MetadataPtr; + copy.IsDebug = config.IsDebug; + copy.LogToSystemConsole = config.LogToSystemConsole; + copy.ArgumentsCount = config.ArgumentsCount; + copy.Arguments = config.Arguments; + return copy; +} + +static NativeScript* currentNativeScript; +static Config* currentConfig; + @implementation NativeScript extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata"); @@ -82,6 +97,9 @@ - (void)shutdownRuntime { - (instancetype)initializeWithConfig:(Config*)config { if (self = [super init]) { + currentNativeScript = self; + currentConfig = CopyConfig(config); + RuntimeConfig.BaseDir = [config.BaseDir UTF8String]; if (config.ApplicationPath != nil) { RuntimeConfig.ApplicationPath = @@ -98,6 +116,15 @@ - (instancetype)initializeWithConfig:(Config*)config { RuntimeConfig.IsDebug = [config IsDebug]; RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole]; + // Connect the JS-exposed `NativeScriptRuntime.reloadApplication(baseDir?)` + // global (registered by the runtime) to the Objective-C implementation below. + tns::SetReloadApplicationHook([](const std::string& baseDir) -> bool { + NSString* dir = baseDir.empty() + ? nil + : [NSString stringWithUTF8String:baseDir.c_str()]; + return [NativeScriptRuntime reloadApplication:dir] == YES; + }); + Runtime::Initialize(); runtime_ = nullptr; runtime_ = std::make_unique(); @@ -135,3 +162,29 @@ - (void)restartWithConfig:(Config*)config { } @end + +@implementation NativeScriptRuntime + ++ (BOOL)reloadApplication { + return [self reloadApplication:nil]; +} + ++ (BOOL)reloadApplication:(NSString*)baseDir { + if (currentNativeScript == nil || currentConfig == nil) { + return NO; + } + + Config* config = CopyConfig(currentConfig); + if (baseDir != nil && [baseDir length] > 0) { + config.BaseDir = baseDir; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + [currentNativeScript restartWithConfig:config]; + [currentNativeScript runMainApplication]; + }); + + return YES; +} + +@end diff --git a/NativeScript/runtime/Runtime.h b/NativeScript/runtime/Runtime.h index 92d95dba..4370f2e1 100644 --- a/NativeScript/runtime/Runtime.h +++ b/NativeScript/runtime/Runtime.h @@ -8,8 +8,15 @@ #include "SpinLock.h" #include "libplatform/libplatform.h" +#include +#include + namespace tns { +using ReloadApplicationHook = std::function; +void SetReloadApplicationHook(ReloadApplicationHook hook); +bool InvokeReloadApplicationHook(const std::string& baseDir); + class Runtime { public: Runtime(); @@ -67,6 +74,8 @@ class Runtime { void DefineCollectFunction(v8::Local context); void DefineNativeScriptVersion(v8::Isolate* isolate, v8::Local globalTemplate); + void DefineNativeScriptRuntime(v8::Isolate* isolate, + v8::Local globalTemplate); void DefinePerformanceObject(v8::Isolate* isolate, v8::Local globalTemplate); void DefineTimeMethod(v8::Isolate* isolate, diff --git a/NativeScript/runtime/Runtime.mm b/NativeScript/runtime/Runtime.mm index 2de309cc..1c0d0b41 100644 --- a/NativeScript/runtime/Runtime.mm +++ b/NativeScript/runtime/Runtime.mm @@ -292,6 +292,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) { tns::binding::CreateInternalBindingTemplates(isolate, globalTemplateFunction); Local globalTemplate = ObjectTemplate::New(isolate, globalTemplateFunction); DefineNativeScriptVersion(isolate, globalTemplate); + DefineNativeScriptRuntime(isolate, globalTemplate); // Worker::Init(isolate, globalTemplate, isWorker); DefinePerformanceObject(isolate, globalTemplate); @@ -573,6 +574,41 @@ void DisposeIsolateWhenPossible(Isolate* isolate) { ToV8String(isolate, STRINGIZE_VALUE_OF(NATIVESCRIPT_VERSION)), readOnlyFlags); } +static ReloadApplicationHook reloadApplicationHook_; + +void SetReloadApplicationHook(ReloadApplicationHook hook) { + reloadApplicationHook_ = std::move(hook); +} + +bool InvokeReloadApplicationHook(const std::string& baseDir) { + if (!reloadApplicationHook_) { + return false; + } + return reloadApplicationHook_(baseDir); +} + +// API to trigger application reload from JS without restarting the application process. +// Exposes `global.NativeScriptRuntime.reloadApplication(baseDir?)` to JS. +// `NativeScriptRuntime` class is part of the runtime framework and +// is intentionally excluded from metadata generation, so it is not reachable +// from JS on its own. +void Runtime::DefineNativeScriptRuntime(Isolate* isolate, Local globalTemplate) { + Local runtimeTemplate = ObjectTemplate::New(isolate); + + Local reloadTemplate = + FunctionTemplate::New(isolate, [](const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + std::string baseDir; + if (info.Length() > 0 && info[0]->IsString()) { + baseDir = tns::ToString(isolate, info[0]); + } + info.GetReturnValue().Set(tns::InvokeReloadApplicationHook(baseDir)); + }); + runtimeTemplate->Set(ToV8String(isolate, "reloadApplication"), reloadTemplate); + + globalTemplate->Set(ToV8String(isolate, "NativeScriptRuntime"), runtimeTemplate); +} + void Runtime::DefineTimeMethod(v8::Isolate* isolate, v8::Local globalTemplate) { Local timeFunctionTemplate = FunctionTemplate::New(isolate, [](const FunctionCallbackInfo& info) {