Native: move samples to backend.native/tests/

This commit is contained in:
Svyatoslav Scherbina
2022-08-26 11:50:39 +02:00
committed by Space
parent b7337d2e64
commit 7bf6d64cfb
94 changed files with 94 additions and 93 deletions
@@ -0,0 +1,13 @@
plugins {
kotlin("multiplatform")
}
kotlin {
macosX64("objc") {
binaries {
executable {
entryPoint = "sample.objc.main"
}
}
}
}
@@ -0,0 +1,2 @@
kotlin.code.style=official
kotlin.import.noCommonSourceSets=true
@@ -0,0 +1,103 @@
package sample.objc
import kotlinx.cinterop.*
import platform.Foundation.NSOperationQueue
import platform.Foundation.NSThread
import platform.darwin.dispatch_async_f
import platform.darwin.dispatch_get_main_queue
import platform.darwin.dispatch_sync_f
import kotlin.native.concurrent.*
import kotlin.test.assertNotNull
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
dispatch_async_f(queue.underlyingQueue, StableRef.create(
producerConsumer()
).asCPointer(), staticCFunction { it ->
val result = it!!.asStableRef<Pair<T, (T) -> Unit>>()
result.get().second(result.get().first)
result.dispose()
})
}
inline fun mainContinuation(singleShot: Boolean = true, noinline block: () -> Unit) = Continuation0(
block, staticCFunction { invokerArg ->
if (NSThread.isMainThread()) {
invokerArg!!.callContinuation0()
} else {
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
args!!.callContinuation0()
})
}
}, singleShot)
inline fun <T1> mainContinuation(singleShot: Boolean = true, noinline block: (T1) -> Unit) = Continuation1(
block, staticCFunction { invokerArg ->
if (NSThread.isMainThread()) {
invokerArg!!.callContinuation1<T1>()
} else {
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
args!!.callContinuation1<T1>()
})
}
}, singleShot)
inline fun <T1, T2> mainContinuation(singleShot: Boolean = true, noinline block: (T1, T2) -> Unit) = Continuation2(
block, staticCFunction { invokerArg ->
if (NSThread.isMainThread()) {
invokerArg!!.callContinuation2<T1, T2>()
} else {
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
args!!.callContinuation2<T1, T2>()
})
}
}, singleShot)
// This object allows to create frozen Kotlin continuations suitable for execution on other threads/queues.
// It takes frozen operation and any after call, and creates lambda which could be used to run operation
// anywhere and provide result to `after` callback.
@ThreadLocal
object Continuator {
val map = mutableMapOf<Any, Pair<Int, *>>()
fun wrap(operation: () -> Unit, after: () -> Unit): () -> Unit {
assert(NSThread.isMainThread())
val id = Any()
map[id] = Pair(0, after)
return {
initRuntimeIfNeeded()
operation()
executeAsync(NSOperationQueue.mainQueue) {
Pair(id, { id: Any -> Continuator.execute(id) })
}
}
}
fun <P> wrap(operation: () -> P, block: (P) -> Unit): () -> Unit {
assert(NSThread.isMainThread())
val id = Any()
map[id] = Pair(1, block)
return {
initRuntimeIfNeeded()
// Note, that operation here must return detachable value (for example, frozen).
executeAsync(NSOperationQueue.mainQueue) {
Pair(Pair(id, operation()), { it: Pair<Any, P> ->
Continuator.execute(it.first, it.second)
})
}
}
}
fun execute(id: Any) {
val countAndBlock = map.remove(id)
assertNotNull(countAndBlock)
assert(countAndBlock.first == 0)
(countAndBlock.second as Function0<Unit>)()
}
fun <P> execute(id: Any, parameter: P) {
val countAndBlock = map.remove(id)
assertNotNull(countAndBlock)
assert(countAndBlock.first == 1)
(countAndBlock.second as Function1<P, Unit>)(parameter)
}
}
@@ -0,0 +1,198 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package sample.objc
import kotlinx.cinterop.*
import platform.AppKit.*
import platform.Contacts.CNContactStore
import platform.Contacts.CNEntityType
import platform.Foundation.*
import platform.darwin.*
import platform.posix.QOS_CLASS_BACKGROUND
import platform.posix.memcpy
import kotlin.native.concurrent.*
import kotlin.test.assertNotNull
data class QueryResult(val json: Map<String, *>?, val error: String?)
private val NSData.json: Map<String, *>?
get() = NSJSONSerialization.JSONObjectWithData(this, 0, null) as? Map<String, *>
fun main() {
autoreleasepool {
runApp()
}
}
val appDelegate = MyAppDelegate()
private fun runApp() {
val app = NSApplication.sharedApplication()
app.delegate = appDelegate
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
app.activateIgnoringOtherApps(true)
app.run()
}
class Controller : NSObject() {
private var index = 1
private val httpDelegate = HttpDelegate()
@ObjCAction
fun onClick() {
if (!appDelegate.canClick) {
appDelegate.contentText.string = "Another load in progress..."
return
}
// Here we call continuator service to ensure we can access mutable state from continuation.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND.convert(), 0),
Continuator.wrap({ println("In queue ${dispatch_get_current_queue()}")}) {
println("After in queue ${dispatch_get_current_queue()}: $index")
})
appDelegate.canClick = false
// Fetch URL in the background on the button click.
httpDelegate.fetchUrl("https://jsonplaceholder.typicode.com/todos/${index++}")
}
@ObjCAction
fun onQuit() {
NSApplication.sharedApplication().stop(this)
}
@ObjCAction
fun onRequest() {
val addressBookRef = CNContactStore()
addressBookRef.requestAccessForEntityType(CNEntityType.CNEntityTypeContacts, mainContinuation {
granted, error ->
appDelegate.contentText.string = if (granted)
"Access granted!"
else
"Access denied: $error"
})
}
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
private val asyncQueue = NSOperationQueue()
private var receivedData: NSData? = null
fun fetchUrl(url: String) {
receivedData = null
val session = NSURLSession.sessionWithConfiguration(
NSURLSessionConfiguration.defaultSessionConfiguration(),
this,
delegateQueue = asyncQueue
)
session.dataTaskWithURL(NSURL(string = url)).resume()
}
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
initRuntimeIfNeeded()
receivedData = didReceiveData
}
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
initRuntimeIfNeeded()
executeAsync(NSOperationQueue.mainQueue) {
val response = task.response as? NSHTTPURLResponse
Pair(when {
response == null -> QueryResult(null, didCompleteWithError?.localizedDescription)
response.statusCode.toInt() != 200 -> QueryResult(null, "${response.statusCode.toInt()})")
else -> QueryResult(receivedData?.json, null)
}, { result: QueryResult ->
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
appDelegate.canClick = true
})
}
}
}
}
class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
private val window: NSWindow
private val controller = Controller()
val contentText: NSText
var canClick = true
init {
val mainDisplayRect = NSScreen.mainScreen()!!.frame
val windowRect = mainDisplayRect.useContents {
NSMakeRect(
origin.x + size.width * 0.25,
origin.y + size.height * 0.25,
size.width * 0.5,
size.height * 0.5
)
}
val windowStyle = NSWindowStyleMaskTitled or NSWindowStyleMaskMiniaturizable or
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
title = "URL async fetcher"
opaque = true
hasShadow = true
preferredBackingLocation = NSWindowBackingLocationVideoMemory
hidesOnDeactivate = false
backgroundColor = NSColor.grayColor()
releasedWhenClosed = false
val delegateImpl = object : NSObject(), NSWindowDelegateProtocol {
override fun windowShouldClose(sender: NSWindow): Boolean {
NSApplication.sharedApplication().stop(this)
return true
}
}
// Wrapping to autoreleasepool is a workaround for false-positive memory leak detected:
// NSWindow.delegate setter appears to put the delegate to autorelease pool.
// Since this code runs during top-level val initializer, it misses the autoreleasepool in [main],
// so the object gets released too late.
autoreleasepool {
delegate = delegateImpl
}
}
val buttonPress = NSButton(NSMakeRect(10.0, 10.0, 100.0, 40.0)).apply {
title = "Click"
target = controller
action = NSSelectorFromString("onClick")
}
window.contentView!!.addSubview(buttonPress)
val buttonQuit = NSButton(NSMakeRect(120.0, 10.0, 100.0, 40.0)).apply {
title = "Quit"
target = controller
action = NSSelectorFromString("onQuit")
}
window.contentView!!.addSubview(buttonQuit)
val buttonRequest = NSButton(NSMakeRect(230.0, 10.0, 100.0, 40.0)).apply {
title = "Request"
target = controller
action = NSSelectorFromString("onRequest")
}
window.contentView!!.addSubview(buttonRequest)
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
string = "Press 'Click' to start fetching"
verticallyResizable = false
horizontallyResizable = false
}
window.contentView!!.addSubview(contentText)
}
override fun applicationWillFinishLaunching(notification: NSNotification) {
window.makeKeyAndOrderFront(this)
}
}