Concurrent API update. (#1949)
This commit is contained in:
@@ -50,9 +50,9 @@ fun main(args: Array<String>) {
|
||||
sharedData.f = 0.5f
|
||||
sharedData.string = "Hello Kotlin!".cstr.getPointer(arena)
|
||||
// Here we create detached mutable object, which could be later reattached by another thread.
|
||||
sharedData.kotlinObject = detachObjectGraph {
|
||||
sharedData.kotlinObject = DetachedObjectGraph {
|
||||
SharedData("A string", 42, SharedDataMember(2.39))
|
||||
}
|
||||
}.asCPointer()
|
||||
// Here we create shared frozen object reference,
|
||||
val stableRef = StableRef.create(SharedData("Shared", 239, SharedDataMember(2.71)).freeze())
|
||||
sharedData.frozenKotlinObject = stableRef.asCPointer()
|
||||
@@ -67,12 +67,12 @@ fun main(args: Array<String>) {
|
||||
argC ->
|
||||
initRuntimeIfNeeded()
|
||||
dumpShared("thread2")
|
||||
val kotlinObject = attachObjectGraph<SharedData>(sharedData.kotlinObject)
|
||||
val arg = attachObjectGraph<SharedDataMember>(argC)
|
||||
val kotlinObject = DetachedObjectGraph<SharedData>(sharedData.kotlinObject).attach()
|
||||
val arg = DetachedObjectGraph<SharedDataMember>(argC).attach()
|
||||
println("thread arg is $arg Kotlin object is $kotlinObject frozen is $globalObject")
|
||||
// Workaround for compiler issue.
|
||||
null as COpaquePointer?
|
||||
}, detachObjectGraph { SharedDataMember(3.14)} ).ensureUnixCallResult("pthread_create")
|
||||
}, DetachedObjectGraph { SharedDataMember(3.14)}.asCPointer() ).ensureUnixCallResult("pthread_create")
|
||||
pthread_join(thread.value, null).ensureUnixCallResult("pthread_join")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import kotlin.native.concurrent.attachObjectGraph
|
||||
import kotlin.native.concurrent.detachObjectGraph
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.AppKit.*
|
||||
import platform.Foundation.*
|
||||
@@ -32,12 +31,12 @@ private class Controller : NSObject() {
|
||||
@ObjCAction
|
||||
fun onClick() {
|
||||
// Execute some async action on button click.
|
||||
dispatch_async_f(asyncQueue, detachObjectGraph {
|
||||
dispatch_async_f(asyncQueue, DetachedObjectGraph {
|
||||
Data(clock_gettime_nsec_np(CLOCK_REALTIME.convert()))
|
||||
}, staticCFunction {
|
||||
}.asCPointer(), staticCFunction {
|
||||
it ->
|
||||
initRuntimeIfNeeded()
|
||||
val data = attachObjectGraph<Data>(it)
|
||||
val data = DetachedObjectGraph<Data>(it).attach()
|
||||
println("in async: $data")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -325,19 +325,14 @@ private class Decoder(
|
||||
fun audioVideoSynced() = (audio?.isSynced() ?: true) || done()
|
||||
}
|
||||
|
||||
class DecoderWorker : Disposable {
|
||||
class DecoderWorker(val worker: Worker) : Disposable {
|
||||
// This class must have no other state, but this worker object.
|
||||
// All the real state must be stored on the worker's side.
|
||||
private val worker: Worker
|
||||
|
||||
constructor() { worker = startWorker() }
|
||||
constructor(id: WorkerId) { worker = Worker(id) }
|
||||
constructor() : this(Worker.start())
|
||||
|
||||
override fun dispose() {
|
||||
worker.requestTermination().result()
|
||||
worker.requestTermination().result
|
||||
}
|
||||
|
||||
val workerId get() = worker.id
|
||||
|
||||
fun initDecode(context: AVFormatContext, useVideo: Boolean = true, useAudio: Boolean = true): CodecInfo {
|
||||
// Find the first video/audio streams.
|
||||
@@ -362,7 +357,7 @@ class DecoderWorker : Disposable {
|
||||
}
|
||||
|
||||
// Pack all state and pass it to the worker.
|
||||
worker.schedule(TransferMode.CHECKED, {
|
||||
worker.execute(TransferMode.SAFE, {
|
||||
Decoder(context.ptr,
|
||||
videoStreamIndex, audioStreamIndex,
|
||||
videoContext, audioContext)
|
||||
@@ -371,7 +366,7 @@ class DecoderWorker : Disposable {
|
||||
}
|
||||
|
||||
fun start(videoOutput: VideoOutput, audioOutput: AudioOutput) {
|
||||
worker.schedule(TransferMode.CHECKED,
|
||||
worker.execute(TransferMode.SAFE,
|
||||
{ Pair(
|
||||
videoOutput.toVideoDecoderOutput(),
|
||||
audioOutput.toAudioDecoderOutput())
|
||||
@@ -381,27 +376,26 @@ class DecoderWorker : Disposable {
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
worker.schedule(TransferMode.CHECKED, { null }) {
|
||||
worker.execute(TransferMode.SAFE, { null }) {
|
||||
decoder?.run {
|
||||
dispose()
|
||||
decoder = null
|
||||
}
|
||||
}.result()
|
||||
}.result
|
||||
}
|
||||
|
||||
fun done(): Boolean =
|
||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.done() ?: true }.result()
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.done() ?: true }.result
|
||||
|
||||
fun requestDecodeChunk() {
|
||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.decodeIfNeeded() }.result()
|
||||
}
|
||||
fun requestDecodeChunk() =
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.decodeIfNeeded() }.result
|
||||
|
||||
fun nextVideoFrame(): VideoFrame? =
|
||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.nextVideoFrame() }.result()
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.nextVideoFrame() }.result
|
||||
|
||||
fun nextAudioFrame(size: Int): AudioFrame? =
|
||||
worker.schedule(TransferMode.CHECKED, { size }) { decoder?.nextAudioFrame(it) }.result()
|
||||
worker.execute(TransferMode.SAFE, { size }) { decoder?.nextAudioFrame(it) }.result
|
||||
|
||||
fun audioVideoSynced(): Boolean =
|
||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.audioVideoSynced() ?: true }.result()
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.audioVideoSynced() ?: true }.result
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import kotlin.native.concurrent.Worker
|
||||
import kotlinx.cinterop.*
|
||||
import sdl.*
|
||||
import platform.posix.memset
|
||||
@@ -32,7 +33,7 @@ private fun SampleFormat.toSDLFormat(): SDL_AudioFormat? = when (this) {
|
||||
}
|
||||
|
||||
class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
||||
private val threadData = arena.alloc<IntVar>().ptr
|
||||
private val workerStable = StableRef.create(player.worker)
|
||||
private var state = State.STOPPED
|
||||
|
||||
fun start(audio: AudioOutput) {
|
||||
@@ -48,10 +49,9 @@ class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
||||
channels = audio.channels.convert()
|
||||
silence = 0u
|
||||
samples = 4096u
|
||||
userdata = threadData
|
||||
userdata = workerStable.asCPointer()
|
||||
callback = staticCFunction(::audioCallback)
|
||||
}
|
||||
threadData.pointed.value = player.workerId
|
||||
val realSpec = alloc<SDL_AudioSpec>()
|
||||
if (SDL_OpenAudio(spec.ptr, realSpec.ptr) < 0)
|
||||
throwSDLError("SDL_OpenAudio")
|
||||
@@ -72,6 +72,7 @@ class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
||||
fun stop() {
|
||||
pause()
|
||||
state = state.transition(State.PAUSED, State.STOPPED) { SDL_CloseAudio() }
|
||||
workerStable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ private fun audioCallback(userdata: COpaquePointer?, buffer: CPointer<Uint8Var>?
|
||||
// This handler will be invoked in the audio thread, so reinit runtime.
|
||||
kotlin.native.initRuntimeIfNeeded()
|
||||
val decoder = decoder ?:
|
||||
DecoderWorker(userdata!!.reinterpret<IntVar>().pointed.value).also { decoder = it }
|
||||
DecoderWorker(userdata!!.asStableRef<Worker>().get()).also { decoder = it }
|
||||
var outPosition = 0
|
||||
while (outPosition < length) {
|
||||
val frame = decoder.nextAudioFrame(length - outPosition)
|
||||
|
||||
@@ -41,15 +41,15 @@ enum class PlayMode {
|
||||
}
|
||||
|
||||
class VideoPlayer(val requestedSize: Dimensions?) : DisposableContainer() {
|
||||
private val decoder = disposable { DecoderWorker() }
|
||||
private val video = disposable { SDLVideo() }
|
||||
private val audio = disposable { SDLAudio(this) }
|
||||
private val input = disposable { SDLInput(this) }
|
||||
private val decoder = disposable { DecoderWorker() }
|
||||
private val now = arena.alloc<platform.posix.timespec>().ptr
|
||||
|
||||
private var state = State.STOPPED
|
||||
|
||||
val workerId get() = decoder.workerId
|
||||
val worker get() = decoder.worker
|
||||
var lastFrameTime = 0.0
|
||||
|
||||
fun stop() {
|
||||
|
||||
@@ -9,15 +9,13 @@ and connected to other worker. This relies on fact that memory management
|
||||
engine can ensure, that one worker doesn't keep references to certain object and
|
||||
whatever it refers to, and so the object could be safely transferred to another worker.
|
||||
|
||||
Workers do not share any state (i.e. globals and Kotlin static objects have different
|
||||
values in different workers), but share executable code of the program and some
|
||||
immutable data, such as immutable binary blobs. But Kotlin objects can be transferred
|
||||
Workers do not share mutable state, but share executable code of the program and some
|
||||
immutable data, such as immutable blobs. But Kotlin objects can be transferred
|
||||
between workers, as long, as they do not refer to objects, having external references.
|
||||
|
||||
The transfer is implemented with the function `schedule()` having the following signature
|
||||
The transfer is implemented with the function `execute()` having the following signature
|
||||
|
||||
fun <T1, T2>
|
||||
schedule(mode: TransferMode,
|
||||
fun <T1, T2> execute(mode: TransferMode,
|
||||
producer: () -> T1,
|
||||
@VolatileLambda job: (T1) -> T2): Future<T2>
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ data class WorkerResult(val intResult: Int, val stringResult: String)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val COUNT = 5
|
||||
val workers = Array(COUNT, { _ -> startWorker()})
|
||||
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||
|
||||
for (attempt in 1 .. 3) {
|
||||
val futures = Array(workers.size, { workerIndex -> workers[workerIndex].schedule(TransferMode.CHECKED, {
|
||||
val futures = Array(workers.size, { workerIndex -> workers[workerIndex].execute(TransferMode.SAFE, {
|
||||
WorkerArgument(workerIndex, "attempt $attempt") }) { input ->
|
||||
var sum = 0
|
||||
for (i in 0..input.intParam * 1000) {
|
||||
@@ -24,12 +24,13 @@ fun main(args: Array<String>) {
|
||||
ready.forEach {
|
||||
it.consume { result ->
|
||||
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
|
||||
consumed++ }
|
||||
consumed++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
workers.forEach {
|
||||
it.requestTermination().consume { _ -> }
|
||||
it.requestTermination().result
|
||||
}
|
||||
println("OK")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user