Share
Embed
(Extension DC 2025) Actor境界を越える技術
Extension DC 2025 Day 1にて発表したトークの発表資料です。
https://dena.connpass.com/event/362412/
Himeshi
October 01, 2025
Transcript
52 slides
-
1VMTF$PEF.PEVMBUJPO 1$. ৼ෯ ࣌ؒ v 0 v 1 v 2
v 3 v 4 v 5 v 6 v 7 v 8 v 9 v 10 v 11 var soundData: [Float] -
"7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =
48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } -
"7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =
48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } -
"7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =
48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } -
"7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =
48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } -
"DUPSͰҐ૬Λอ࣋ actor SineWavePlayer { var phase: Float = 0 var
sourceNode: AVAudioSourceNode { AVAudioSourceNode { ... } } func play() throws { phases = 0 let audioEngine = AVAudioEngine() let sourceNode = sourceNode audioEngine.attach(sourceNode) audioEngine.connect(sourceNode, to: audioEngine.mainMixerNode, format: ...) audioEngine.prepare() try audioEngine.start() } } -
"DUPSDPOUFYU "DUPS" var a: Int = 42 func foo() var
b: Int = 123 "DUPS" "DUPS# ࣌ؒ bar() getter B Actor B bar() (Other) func bar() ಉظ ඇಉظ -
એݴɾΫϩʔδϟʔͱ"DUPSDPOUFYU actor MyActor { var value: Int = 42 var
nonSendableBlock: () -> Void { // Assumed to be executed on MyActor's context { [self] in value = value + 1 } } } -
actor MyActor { var value: Int = 42 var nonSendableBlock:
() -> Void { // Assumed to be executed on MyActor's context { [self] in value = value + 1 } } var sendableBlock: @Sendable () -> Void { // Cannot touch MyActor's property synchronously { /* [self] in value = value + 1 */ } } } એݴɾΫϩʔδϟʔͱ"DUPSDPOUFYU -
actor MyActor { var value: Int = 42 var nonSendableBlock:
() -> Void { // Assumed to be executed on MyActor's context { [self] in value = value + 1 } } var sendableBlock: @Sendable () -> Void { // Cannot touch MyActor's property synchronously { /* [self] in value = value + 1 */ } } nonisolated var nonisolatedBlock: () -> Void { // Cannot touch MyActor's property synchronously { /* [self] in value = value + 1 */ } } } એݴɾΫϩʔδϟʔͱ"DUPSDPOUFYU -
"7'"VEJPͷߏཁૉ "QQMJDBUJPO "7"VEJP&OHJOF "7"VEJP 4PVSDF/PEF 4ZTUFN "VEJP1SPDFTT "UUBDI ఆظతʹ ݺͼग़͠
Իσʔλ Λܭࢉ γεςϜͷΦʔσΟΦॲཧ εϨουͰ࣮ߦ͞ΕΔ -
"7"VEJP4PVSDF/PEF3FOEFS#MPDL typealias AVAudioSourceNodeRenderBlock = ( UnsafeMutablePointer<ObjCBool>, // isSilence UnsafePointer<AudioTimeStamp>, //
timestamp AVAudioFrameCount, // frameCount UnsafeMutablePointer<AudioBufferList> // outputData ) -> OSStatus @Sendable͕ͳ͍4FOEBCMFͳΫϩʔδϟͰͳ͍ -
࣮ߦ"DUPSDPOUFYUҧ w 4PVOESFOEFSJOHCMPDL4JOF8BWF1MBZFSBDUPSʹଐ͢Δɻ 4JOF8BWF1MBZFSͷBDUPSDPOUFYU্Ͱ࣮ߦ͞ΕΔ͖ɻ w ͔࣮͠͠ࡍʹɺ4PVOESFOEFSJOHCMPDLγεςϜͷΦʔσΟ Φॲཧ༻εϨου্Ͱ࣮ߦ͞ΕΔɻ BDUPS4JOF8BWF1MBZFS var phase:
Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE $BQUVSFE -
ͦΕͰಉظؔͰ͋Δඞཁ͕͋Δ actor SineWavePlayer { var phase: Float = 0 nonisolated
var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } Actor-isolated property 'phase' can not be referenced from a nonisolated context -
actor MyActor { func sample() async { funcA() await funcB()
funcC() await funcD() funcE() } } actor MyActor { } &YFDVUPS Job Job Job Job Job Job "DUPS+PCͷ࣮ߦ -
BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT "DDFTT
$BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL%JTQBUDI2VFVFͰ࣮ߦɻ -
%JTQBUDI2VFVF BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT
"DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL%JTQBUDI2VFVFͰ࣮ߦɻ -
%JTQBUDI2VFVF w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL%JTQBUDI2VFVFͰ࣮ߦɻ BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF
3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT "DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ -
&YFDVUPSͷ࣮ final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue
= DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } -
final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue =
DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } &YFDVUPSͷ࣮ -
final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue =
DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } &YFDVUPSͷ࣮ -
final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue =
DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } queueͷ֎ͰݺΕΔͱΫϥογϡ͢Δ &YFDVUPSͷ࣮ -
&YFDVUPSΛࢦఆ actor SineWavePlayer { let executor = PlayerExecutor() nonisolated var
unownedExecutor: UnownedSerialExecutor { executor.asUnownedSerialExecutor() } } -
%JTQBUDI2VFVF BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT
"DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL%JTQBUDI2VFVFͰ࣮ߦɻ -
%JTQBUDI2VFVF BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT
"DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL%JTQBUDI2VFVFͰ࣮ߦɻ -
%JTQBUDI2VFVF্Ͱ4PVOESFOEFSJOH var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [executor] _, _,
frameCount, outputPointer in executor.queue.sync { [weak self] in guard let self else { return noErr } guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(self.phase) // Set the amplitude self.phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } -
4PVOESFOEFSJOHMPHJDΛִ͢Δ var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [executor] _, _,
frameCount, outputPointer in executor.queue.sync { [weak self] in guard let self else { return noErr } return assumeIsolated { this in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(this.phase) // Set the amplitude this.phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } } -
%JTQBUDI2VFVF w શͯΛ%JTQBUDI2VFVF্ͰγϦΞϧʢྻʣʹ࣮ߦ͢Δɻ w assumeIsolatedΛ༻͍ͯಉ͡BDUPSDPOUFYUʹॴଐͤ͞Δɻ BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF
3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT "DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ -
.VUFY w withLockͷίʔυ͕ഉଞతʹ࣮ߦ͞ΕΔɻ w εϨουΛϒϩοΫͯ͠ػ͢Δɻ w 4XJGUDPODVSSFODZͱͷΈ߹ΘͤʹҙΛཁ͢Δɻ w 4FOEBCMFɻ"DUPSڥքΛӽ͑ͯΞΫηεͰ͖Δɻ let
myValue = Mutex<Int>(42) myValue.withLock { $0 = 123 } -
.VUFYͰҐ૬มʹΞΫηε͢Δ DMBTT4JOF8BWF1MBZFS let phase: Mutex<Float> "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE $BQUVSFE w
ΦʔσΟΦॲཧ༻εϨου͔Β.VUFYʹΞΫηε͢Δɻ w 4PVOESFOEFSJOHCMPDLશମΛ.VUFYͰϩοΫ͢Δɻ 4JOF8BWF1MBZFS -
class SineWavePlayer { let phase = Mutex<Float>(0) var sourceNode: AVAudioSourceNode
{ AVAudioSourceNode { [self] _, _, frameCount, outputPointer in phase.withLock { phase in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } } .VUFYʹΑΔ࣮ -
.VUFYʹΑΔ࣮ class SineWavePlayer { let phase = Mutex<Float>(0) var sourceNode:
AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in phase.withLock { phase in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } } -
XJUI-PDLͱॲཧͷΞτϛοΫੑ class SineWavePlayer: Sendable { let phase = Mutex<Float>(0) var
sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { phase.withLock { outputBuffer[index] = sin($0) } phase.withLock { $0 += 2 * .pi * frequency / sampleRate } } return noErr } } } -
ͭͷํ๏ͷൺֱ $VTUPNBDUPS FYFDVUPS .VUFY ࣮ͷ؆୯͞ !͋Δఔ४උ͕ඞཁ "มΛஔ͖͑Δ͚ͩ 4XJGUDPODVSSFODZ ͱͷ౷߹ "͍ͭͷ
BTZODBXBJUελΠϧ ⚠ΞτϛοΫੑͱ σουϩοΫʹཁҙ -
,8%$ w ݄ιϧʹͯ։࠵͞Εͨ "QQMFϓϥοτϑΥʔϜ։ൃऀͷࠃࡍΧϯϑΝϨϯεɻ w γϯάϧτϥοΫɺ։࠵ɻࠓͰճɻ w ొஃऀਓதɺຊ͔Βਓɻ w ࢀՃऀͱͯ͠ຊ͔Β૯ਓ͙Β͍ʁ
w ଞʹதࠃຊɺγϯΨϙʔϧɺΧφμ͔Βདྷ͍ͯͨɻ w 'MJUUPʹΑΔ༁αʔϏεඋɻ