diff --git a/arkoala-arkts/loader/src/loader.ts b/arkoala-arkts/loader/src/loader.ts index faf3c4d98f7789051b24d10567cc7e2b0318f056..8079acf26000e968395690bf2f370dedf980b06f 100644 --- a/arkoala-arkts/loader/src/loader.ts +++ b/arkoala-arkts/loader/src/loader.ts @@ -309,7 +309,7 @@ if (process && process.argv.length > 2) { if (vmApp.length > 1) { app = vmApp[1] } - if (process.argv.length > 2) { + if (process.argv.length > 3) { const testName = process.argv[3] const isString = isNaN(parseInt(testName)) if (isString) { diff --git a/arkoala-kotlin/framework/kotlin/src/Application.kt b/arkoala-kotlin/framework/kotlin/src/Application.kt new file mode 100644 index 0000000000000000000000000000000000000000..8ce0ba92a45faca39d0fc883c7c89c7a6430cf53 --- /dev/null +++ b/arkoala-kotlin/framework/kotlin/src/Application.kt @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package koalaui.arkoala + +import koalaui.interop.* + +typealias UserViewFactory = (appUrl: String) -> UserView + +enum class EventType(val value: Int) { + Click(0), + Text(1), + ExitApp(2), + StartLog(3), + StopLog(4), + GetLog(5), + SyncNeeded(6), +} + +class Application( + private val userView: UserView, + private val appParams: String, + private val useNativeLog: Boolean, +): UserApplicationControl { + companion object { + private var _userViewFactory: UserViewFactory = { appUrl -> + error("No UserView factory, use Application.setUserViewFactory to set") + } + fun setUserViewFactory(factory: UserViewFactory) { + _userViewFactory = factory + } + + fun create(appUrl: String, params: String, useNativeLog: Boolean): Application { + val userView = Application._userViewFactory(appUrl) + val app = Application(userView, params, useNativeLog) + userView.provideControl(app) + return app + } + + fun createMemoRootState(builder: UserViewBuilder): PeerNode { + val root = PeerNode.generateRootPeer() + builder() + return root + } + } + + private var rootState: PeerNode? = null + private var rootPointer: pointer = nullptr + private var exitApp: Boolean = false + private var useOwnLoop: Boolean = true + private var withLog: Boolean = false + + fun start(loopIterations: Int): pointer { + startNativeLog() + try { + val builder = userView.getBuilder() + rootState = createMemoRootState(builder) + rootPointer = rootState!!.peer.ptr + } + catch (error: Exception) { + InteropNativeModule._NativeLog("Application.start() error: ${error.stackTraceToString()}") + return nullptr + } + stopNativeLog() + + if (useOwnLoop) { + runEventLoop(loopIterations) + } + + return rootPointer + } + + fun enter(): Boolean { + try { + startNativeLog() + if (currentCrash != null) { + drawCurrentCrash() + } + else { + try { + loopIteration() + } + catch (error: Exception) { + val crash = error.stackTraceToString() + InteropNativeModule._NativeLog("Application.enter() error: ${crash}") + currentCrash = crash + return true + } + } + stopNativeLog() + } + catch (error: Exception) { + val crash = error.stackTraceToString() + println("Application.enter() stack trace: ${crash}") + exitApp = true + } + return exitApp + } + + fun emitEvent(type: Int, target: Int, arg0: Int, arg1: Int): String { + when (type) { + EventType.Click.value -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] EventType.Click is not implemented.") + EventType.Text.value -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] EventType.Text is not implemented.") + EventType.StartLog.value -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] EventType.StartLog is not implemented.") + EventType.StopLog.value -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] EventType.StopLog is not implemented.") + EventType.GetLog.value -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] EventType.GetLog is not implemented.") + EventType.ExitApp.value -> { + exitApp = true + return if (currentCrash == null) "" else currentCrash!! + } + EventType.SyncNeeded.value -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] EventType.SyncNeeded is not implemented.") + else -> InteropNativeModule._NativeLog("Kotlin: [emitEvent] type = $type is unknown") + } + return "0" + } + + override fun params(): String { + return appParams + } + override fun startLog(): UserApplicationControl { + error("Application.startLog not implemented") + } + override fun getLog(): String { + error("Application.getLog not implemented") + } + override fun stopLog(): UserApplicationControl { + error("Application.stopLog not implemented") + } + override fun emitTask(type: Int, target: String, arg0: Int?, arg1: Int?): UserApplicationControl { + error("Application.emitTask not implemented") + } + //override fun nextFrame(): Promise {} + override fun reloadView(): UserApplicationControl { + error("Application.reloadView not implemented") + } + override fun requestStopApp(crash: String?): Unit { + if (crash != null) { + currentCrash = crash + } + exitApp = true + } + + private fun runEventLoop(loopIterations: Int) { + println("runEventLoop ${loopIterations}") + var iterations = loopIterations + while (!this.exitApp && iterations > 0) { + try { + loopIteration() + iterations -= 1 + } + catch (error: Exception) { + val crash = error.stackTraceToString() + println("Application.runEventLoop() error: ${crash}") + currentCrash = crash + drawCurrentCrash() + exitApp = true + } + } + } + + private fun loopIteration(): Unit { + if (withLog) { + InteropNativeModule._NativeLog("Kotlin: loopIteration") + } + checkEvents() + } + + private var currentCrash: String? = null + private var crashDumped: Boolean = false + private fun drawCurrentCrash() { + if (!this.crashDumped) { + InteropNativeModule._NativeLog(currentCrash!!) + this.crashDumped = true + } + } + + private fun startNativeLog(): Unit { + if (withLog) { + NativeLog.startNativeLog(1) + } + } + private fun stopNativeLog(): Unit { + if (withLog) { + NativeLog.stopNativeLog(1) + if (useNativeLog) { + InteropNativeModule._PrintGroupedLog(1) + } + else { + val log = NativeLog.getNativeLog(1) + if (log.length > 0) { + InteropNativeModule._NativeLog(log) + } + } + } + } +} diff --git a/arkoala-kotlin/framework/kotlin/src/NativeLog.kt b/arkoala-kotlin/framework/kotlin/src/NativeLog.kt new file mode 100644 index 0000000000000000000000000000000000000000..1896c28c1b881adf90ce818d051b58af19edf972 --- /dev/null +++ b/arkoala-kotlin/framework/kotlin/src/NativeLog.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:OptIn(ExperimentalForeignApi::class) + +package koalaui.arkoala + +import kotlinx.cinterop.* +import koalaui.interop.* + +object NativeLog { + private var _isStarted: Boolean = false + + fun startNativeLog(group: Int): Unit { + _isStarted = true + InteropNativeModule._StartGroupedLog(group) + } + + fun stopNativeLog(group: Int): Unit { + _isStarted = false + InteropNativeModule._StopGroupedLog(group) + } + + fun appendNativeLog(group: Int, message: String): Unit { + if (!_isStarted) { + startNativeLog(group) + } + InteropNativeModule._AppendGroupedLog(group, message) + } + + fun getNativeLog(group: Int): String { + val ptr = InteropNativeModule._GetGroupedLog(group) + val length = InteropNativeModule._StringLength(ptr) + val data = KUint8ArrayPtr(length) + InteropNativeModule._StringData(ptr, data, length) + InteropNativeModule._InvokeFinalizer(ptr, InteropNativeModule._GetStringFinalizer()) + return data.asByteArray().toKString() + } + + fun printNativeLog(group: Int): Unit { + InteropNativeModule._PrintGroupedLog(group) + } +} diff --git a/arkoala-kotlin/framework/kotlin/src/NativePeerNode.kt b/arkoala-kotlin/framework/kotlin/src/NativePeerNode.kt index 4e3818bea9eaa4a2e24026d48b31156552a4bac4..3a33ecbacfe72721f0bf80f41d85b75f85ef1021 100644 --- a/arkoala-kotlin/framework/kotlin/src/NativePeerNode.kt +++ b/arkoala-kotlin/framework/kotlin/src/NativePeerNode.kt @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package idlize +package koalaui.arkoala import koalaui.interop.* diff --git a/arkoala-kotlin/framework/kotlin/src/PeerNode.kt b/arkoala-kotlin/framework/kotlin/src/PeerNode.kt index 3a6983e747ab158a06c8c1ee14dac07c90e041e5..4a9e76b49e624d1107b0142079a980e5b0c7de8f 100644 --- a/arkoala-kotlin/framework/kotlin/src/PeerNode.kt +++ b/arkoala-kotlin/framework/kotlin/src/PeerNode.kt @@ -12,14 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package idlize +package koalaui.arkoala import koalaui.interop.* // add inheritance from NativePeerNode -public open class PeerNode { +public open class PeerNode(peerPtr: pointer, val id: Int, val name: String, flags: Int) { public var peer: NativePeerNode + init { + peer = NativePeerNode.create(peerPtr) + } + companion object { protected var currentId: Int = 1000 private var PEER_NODE_TYPE: UInt = 11u @@ -28,10 +32,16 @@ public open class PeerNode { this.currentId += 1 return currentId } + + fun generateRootPeer(): PeerNode { + // just stub for now to make application work + val peerPtr = InteropNativeModule._Malloc(4) + val peerId = PeerNode.nextId() + val root = PeerNode(peerPtr, peerId, "Root", 0) + root.peer.finalizer = InteropNativeModule._GetMallocFinalizer() + return root + } } - constructor(peerPtr: pointer, id: Int, name: String, flags: Int) { - this.peer = NativePeerNode.create(peerPtr) - } - fun applyAttributes(attrs: HashMap) {} + fun applyAttributes(attrs: HashMap) {} } \ No newline at end of file diff --git a/arkoala-kotlin/framework/kotlin/src/UserView.kt b/arkoala-kotlin/framework/kotlin/src/UserView.kt new file mode 100644 index 0000000000000000000000000000000000000000..ded546b16539b9de038d743114a59015bf64c7a9 --- /dev/null +++ b/arkoala-kotlin/framework/kotlin/src/UserView.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package koalaui.arkoala + +typealias UserViewBuilder = () -> Unit + +interface UserApplicationControl { + fun params(): String + fun startLog(): UserApplicationControl + fun getLog(): String + fun stopLog(): UserApplicationControl + fun emitTask(type: Int, target: String, arg0: Int?, arg1: Int?): UserApplicationControl + //fun nextFrame(): Promise + fun reloadView(): UserApplicationControl + fun requestStopApp(crash: String?): Unit +} + +open class UserView { + protected var control: UserApplicationControl? = null + final fun provideControl(control: UserApplicationControl): Unit { + this.control = control + } + open fun getBuilder(): UserViewBuilder { + error("User must override this method"); + } +} \ No newline at end of file diff --git a/arkoala-kotlin/framework/kotlin/src/VMLoaderApplication.kt b/arkoala-kotlin/framework/kotlin/src/VMLoaderWrapper.kt similarity index 45% rename from arkoala-kotlin/framework/kotlin/src/VMLoaderApplication.kt rename to arkoala-kotlin/framework/kotlin/src/VMLoaderWrapper.kt index 290b69fcdd8df8e7562ff1302f734bc7d55e1d17..51d64bc490c34092996bcf0cad7b14293a72649e 100644 --- a/arkoala-kotlin/framework/kotlin/src/VMLoaderApplication.kt +++ b/arkoala-kotlin/framework/kotlin/src/VMLoaderWrapper.kt @@ -12,46 +12,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import koalaui.interop.* +package koalaui.arkoala -interface PeerNodeStub {} - -interface VMLoaderApplication { - fun start(): PeerNodeStub - fun enter(): Boolean -} - -internal class PeerNodeStubImpl: PeerNodeStub {} - -internal class VMLoaderApplicationImpl(private val appUrl: String, private val appParams: String): VMLoaderApplication { - init { - println("Kotlin koala: Application.constructor($appUrl, $appParams)") - } - override fun start(): PeerNodeStub { - println("Kotlin koala: Application.start()") - InteropNativeModule._NativeLog("Kotlin koala: call _NativeLog") - return PeerNodeStubImpl() - } - override fun enter(): Boolean { - println("Kotlin koala: Application.enter()") - return true - } -} +import koalaui.interop.pointer @kotlin.experimental.ExperimentalNativeApi @CName("application_create") -fun application_create(appUrl: String, appParams: String): VMLoaderApplication { - return VMLoaderApplicationImpl(appUrl, appParams) +fun application_create(appUrl: String, appParams: String, useNativeLog: Boolean): Application { + return Application.create(appUrl, appParams, useNativeLog) } @kotlin.experimental.ExperimentalNativeApi @CName("application_start") -fun application_start(app: VMLoaderApplication): PeerNodeStub { - return app.start() +fun application_start(app: Application, loopIterations: Int): pointer { + return app.start(loopIterations) } @kotlin.experimental.ExperimentalNativeApi @CName("application_enter") -fun application_enter(app: VMLoaderApplication): Boolean { +fun application_enter(app: Application): Boolean { return app.enter() } + +@kotlin.experimental.ExperimentalNativeApi +@CName("application_emit_event") +fun application_emit_event(app: Application, type: Int, target: Int, arg0: Int, arg1: Int): String { + return app.emitEvent(type, target, arg0, arg1) +} diff --git a/arkoala-kotlin/framework/package.json b/arkoala-kotlin/framework/package.json index 24fa4a806130839427ce75e0b5bc560c9d8844e2..5cc790733f93ee12e00b0c920e642abc8c655fda 100644 --- a/arkoala-kotlin/framework/package.json +++ b/arkoala-kotlin/framework/package.json @@ -1,10 +1,11 @@ { + "config": { + "framework_files": "kotlin/src/Application.kt kotlin/src/NativeLog.kt kotlin/src/NativePeerNode.kt kotlin/src/PeerNode.kt kotlin/src/UserView.kt" + }, "scripts": { - "test": "npm run compile:kotlin:interop -C ../../interop && npm run build:native && npm run build:kt && npm run build:loader && npm run run", "clean": "rm -rf build", "build:native": "npm run compile:native-kotlin-with-node-host --prefix ../../arkoala-arkts/framework", - "build:kt": "mkdir -p build && konanc kotlin/src/VMLoaderApplication.kt -l ../../interop/build/kotlin-interop/interop.klib -l ../../interop/build/kotlin-interop/cinterop.interop_native_module.klib -linker-options '-L../../arkoala-arkts/framework/native/build-kotlin-host -lArkoalaNative_linux_x64_kotlin' -p dynamic -o build/kotlin_koala", - "build:loader": "npm run build:loader:node-kotlin -C ../../arkoala-arkts/loader", - "run": "ACE_LIBRARY_PATH=../../arkoala-arkts/build LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./build:../../arkoala-arkts/build node ../../arkoala-arkts/build/index.js kotlin:ComExampleTrivialApplication 10" + "build:framework": "konanc $npm_package_config_framework_files -l ../../interop/build/kotlin-interop/interop.klib -l ../../interop/build/kotlin-interop/cinterop.interop_native_module.klib -p library -o build/arkoala", + "build:loader": "npm run build:loader:node-kotlin -C ../../arkoala-arkts/loader" } } diff --git a/interop/src/cpp/kotlin/kotlin_vmloader_wrapper.h b/interop/src/cpp/kotlin/kotlin_vmloader_wrapper.h index 980a5abdc3d5f875d6cecaae2c76654ffc3f8e41..cab6ff22892167d37ae3f7d8c66e356ff26fa262 100644 --- a/interop/src/cpp/kotlin/kotlin_vmloader_wrapper.h +++ b/interop/src/cpp/kotlin/kotlin_vmloader_wrapper.h @@ -33,9 +33,19 @@ typedef struct { kotlin_KNativePtr pinned; } kotlin_kref_PeerNodeStub; -typedef kotlin_kref_VMLoaderApplication (*application_create_t)(const char* appUrl, const char* appParams); +typedef kotlin_kref_VMLoaderApplication (*application_create_t)(const char* appUrl, const char* appParams, kotlin_KBoolean useNativeLog); +typedef const char* (*application_emit_event_t)(kotlin_kref_VMLoaderApplication app, kotlin_KInt type, kotlin_KInt target, kotlin_KInt arg0, kotlin_KInt arg1); typedef kotlin_KBoolean (*application_enter_t)(kotlin_kref_VMLoaderApplication app); -typedef kotlin_kref_PeerNodeStub (*application_start_t)(kotlin_kref_VMLoaderApplication app); +typedef kotlin_kref_PeerNodeStub (*application_start_t)(kotlin_kref_VMLoaderApplication app, kotlin_KInt loopIterations); +typedef void (*set_user_view_factory_t)(void); + +typedef struct { + /* Service functions. */ + void (*DisposeStablePointer)(kotlin_KNativePtr ptr); + void (*DisposeString)(const char* string); +} kotlin_ExportedSymbols; + +typedef kotlin_ExportedSymbols* (*kotlin_exported_symbols_t)(void); #ifdef __cplusplus } /* extern "C" */ diff --git a/interop/src/cpp/vmloader.cc b/interop/src/cpp/vmloader.cc index 6a5cf93c12c986eeff240538444c60f57cc76986..fedde77374536db647c280c0375a7364576acbda 100644 --- a/interop/src/cpp/vmloader.cc +++ b/interop/src/cpp/vmloader.cc @@ -510,12 +510,16 @@ extern "C" DLL_EXPORT KInt LoadVirtualMachine(KInt vmKind, const char* bootFiles g_vmEntry.vmKind = vmKind; (void)vm; - application_create_t application_create = (application_create_t)findSymbol(handle, "application_create"); - application_start_t application_start = (application_start_t)findSymbol(handle, "application_start"); - application_enter_t application_enter = (application_enter_t)findSymbol(handle, "application_enter"); - g_vmEntry.create = (void*)application_create; - g_vmEntry.start = (void*)application_start; - g_vmEntry.enter = (void*)application_enter; + kotlin_exported_symbols_t kotlin_exported_symbols = (kotlin_exported_symbols_t)findSymbol(handle, "kotlin_koala_symbols"); + env = kotlin_exported_symbols(); + + set_user_view_factory_t set_user_view_factory = (set_user_view_factory_t)findSymbol(handle, "set_user_view_factory"); + set_user_view_factory(); + + g_vmEntry.create = findSymbol(handle, "application_create"); + g_vmEntry.start = findSymbol(handle, "application_start"); + g_vmEntry.enter = findSymbol(handle, "application_enter"); + g_vmEntry.emitEvent = findSymbol(handle, "application_emit_event"); result = 0; } @@ -880,10 +884,11 @@ extern "C" DLL_EXPORT KNativePointer StartApplication(const char* appUrl, const application_create_t application_create = (application_create_t)g_vmEntry.create; application_start_t application_start = (application_start_t)g_vmEntry.start; - kotlin_kref_VMLoaderApplication app = application_create(appUrl, appParams); + bool useNativeLog = false; + kotlin_kref_VMLoaderApplication app = application_create(appUrl, appParams, useNativeLog); g_vmEntry.app = app.pinned; - kotlin_kref_PeerNodeStub root = application_start(app); + kotlin_kref_PeerNodeStub root = application_start(app, loopIterations); return root.pinned; } #endif @@ -1051,6 +1056,23 @@ extern "C" DLL_EXPORT const char* EmitEvent(const KInt type, const KInt target, return buffer; } #endif + +#ifdef KOALA_KOTLIN + if (g_vmEntry.vmKind == KOTLIN_KIND) { + kotlin_kref_VMLoaderApplication app = { .pinned = g_vmEntry.app }; + application_emit_event_t application_emit_event = (application_emit_event_t)g_vmEntry.emitEvent; + const char *kotlinString = application_emit_event(app, type, target, arg0, arg1); + + size_t bufferSize = interop_strlen(kotlinString) + 1; + char *result = (char*)malloc(bufferSize); + interop_strcpy(result, bufferSize, kotlinString); + + kotlin_ExportedSymbols *env = reinterpret_cast(g_vmEntry.env); + env->DisposeString(kotlinString); + + return result; + } +#endif return "-1"; } diff --git a/interop/src/kotlin/Finalizable.kt b/interop/src/kotlin/Finalizable.kt index d83e3b356eaed707b8ed181289f58441f7a40772..c351ca60bd3aac258e470948462eeac5e91c8d7f 100644 --- a/interop/src/kotlin/Finalizable.kt +++ b/interop/src/kotlin/Finalizable.kt @@ -23,6 +23,8 @@ private class _FinalizableDataHolder(val ptr: KPointer, val finalizer: KPointer) public open class Finalizable(var ptr: KPointer, var finalizer: KPointer) { @OptIn(ExperimentalNativeApi::class) var cleaner = createCleaner(_FinalizableDataHolder(ptr, finalizer)) { - InteropNativeModule._InvokeFinalizer(it.ptr, it.finalizer) + if (it.finalizer != nullptr) { + InteropNativeModule._InvokeFinalizer(it.ptr, it.finalizer) + } } } diff --git a/interop/src/kotlin/InteropNativeModule.kt b/interop/src/kotlin/InteropNativeModule.kt index 63c913070cb33cfcea4a043770b97e12866ff230..178c132608ad58945c4e07d068cf136b5f1b3f52 100644 --- a/interop/src/kotlin/InteropNativeModule.kt +++ b/interop/src/kotlin/InteropNativeModule.kt @@ -60,6 +60,9 @@ public class InteropNativeModule { return kotlin_StringLength(ptr1.toCPointer()!!) } public fun _StringData(ptr1: KPointer, array: KUint8ArrayPtr, arrayLength: Int): Unit { + if (arrayLength <= 0) { + return + } array.usePinned { kotlin_StringData(ptr1.toCPointer()!!, it.addressOf(0), arrayLength) }