Post

Replies

Boosts

Views

Activity

Is there any interest in offering a Swift overlay for HIServices AX Apis?
The existing API is roughly imported into Swift and pretty thorny. Some of this can be addressed with a wrapper library but some of it is much more difficult, like addressing AXObserver not having a finished event or callback to signify when it would be safe to do cleanup in a concurrent environment. A Swift overlay could be a great opportunity to make third part assistive tech more accessible.
3
0
42
3h
Screen Reader for macOS implemented with Swift Concurrency and Distributed Actors
Repurposing my questions that weren't a good fit for the group lab to see how that goes :) I've been building a ScreenReader in Swift leveraging Structured Concurrency, actors, and recently distributed actors over XPC. https://codeberg.org/SpeakUp I have a number of questions I could ask (and would love to ask) but would start with asking for thoughts on my RunLoopExecutor project https://codeberg.org/SpeakUp/RunLoopExecutor/ All of the macOS Accessibility APIs are C/CoreFoundation/CFRunLoop based and I wanted to build something where actors would feel idiomatic for an experienced Swift developer but under the hood we're making sure that we're not contending with ourselves with all the IPC we're doing to get Accessibility data. I think so far it's been pretty successful as seen in the Controller types for the ScreenReader project: https://codeberg.org/SpeakUp/ScreenReader I'm currently using pretty naive pool implementations, one that is fixed width and one that is dynamic with a maximum width. Would love to hear different approaches to growing and shrinking the thread pool and handling things like marking a given executor as likely in a bad state (usually meaning the app it's talking to over AX API is blocking it's main thread) In the AccessibilityElement project https://codeberg.org/SpeakUp/AccessibilityElement for my HIServices Observer implementation I'm exposed to a race condition where axobserver doesn't flush it's notification queue on remove. I'm relying on pthread_specific currently to introduce thread local storage to work around this but it's quite clunky. In an ideal world the HIServices API would emit a done event to allow cleanup but so far that hasn't happened. I'll leave it there for now and do new posts with more requests for feedback if this one is well received.
2
0
48
3h
Deduplication of Tasks
A pretty common piece of work I'd like to generalize a Task version of is deduplicating work in callback logic by stashing the equivalent callbacks to be called when the shared underlying work is done. I have a fairly naive version of this working as it's own Task/actor/async/await soup import Foundation public actor Dupes<Key: Hashable, Success, Failure: Error> {     private var tasks = [Key:Task<Success, Failure>]() } public extension Dupes where Failure == Error {     func async(key: Key, priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success) async throws -> Success {         if let task = tasks[key] {             return try await task.value         } else {             let task = Task(priority: priority) {                 try await operation()             }             tasks[key] = task             let data = try await task.value             tasks.removeValue(forKey: key)             return data         }     } } public extension Dupes where Failure == Never {     func async(key: Key, priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> Success) async -> Success {         if let task = tasks[key] {             return await task.value         } else {             let task = Task(priority: priority) {                 await operation()             }             tasks[key] = task             let data = await task.value             tasks.removeValue(forKey: key)             return data         }     } } That can used like so if we had an async method that returns a value (bar()) based on some work that we know is shared based on the identifier 123 let foo = Foo() let dupes = Dupes<String, Int, Never>() Task.detached { await dupes.async(key: "123") {     await foo.bar() } } Task.detached { await dupes.async(key: "123") {     await foo.bar() } } In naive testing this seems to work. It feels really odd to be handing off to another actor for managing state that seems internal to whatever actor would need to avoid duplicate work. I tried defining similar methods on a custom actor or on an extension on Actor but passing the storage via keyPath or inout violates the mutability rules of actors (which to be fair makes sense). Is there an idiom for this kind of book keeping? A good real world example of this is network requests where I'd pass in a Hashable network request that makes or owns a URLRequest for me to hand off to URLSession. I don't want to download the same request twice and I don't want to write the same book keeping logic again and again.
0
0
910
Jul ’21
Is there any interest in offering a Swift overlay for HIServices AX Apis?
The existing API is roughly imported into Swift and pretty thorny. Some of this can be addressed with a wrapper library but some of it is much more difficult, like addressing AXObserver not having a finished event or callback to signify when it would be safe to do cleanup in a concurrent environment. A Swift overlay could be a great opportunity to make third part assistive tech more accessible.
Replies
3
Boosts
0
Views
42
Activity
3h
Screen Reader for macOS implemented with Swift Concurrency and Distributed Actors
Repurposing my questions that weren't a good fit for the group lab to see how that goes :) I've been building a ScreenReader in Swift leveraging Structured Concurrency, actors, and recently distributed actors over XPC. https://codeberg.org/SpeakUp I have a number of questions I could ask (and would love to ask) but would start with asking for thoughts on my RunLoopExecutor project https://codeberg.org/SpeakUp/RunLoopExecutor/ All of the macOS Accessibility APIs are C/CoreFoundation/CFRunLoop based and I wanted to build something where actors would feel idiomatic for an experienced Swift developer but under the hood we're making sure that we're not contending with ourselves with all the IPC we're doing to get Accessibility data. I think so far it's been pretty successful as seen in the Controller types for the ScreenReader project: https://codeberg.org/SpeakUp/ScreenReader I'm currently using pretty naive pool implementations, one that is fixed width and one that is dynamic with a maximum width. Would love to hear different approaches to growing and shrinking the thread pool and handling things like marking a given executor as likely in a bad state (usually meaning the app it's talking to over AX API is blocking it's main thread) In the AccessibilityElement project https://codeberg.org/SpeakUp/AccessibilityElement for my HIServices Observer implementation I'm exposed to a race condition where axobserver doesn't flush it's notification queue on remove. I'm relying on pthread_specific currently to introduce thread local storage to work around this but it's quite clunky. In an ideal world the HIServices API would emit a done event to allow cleanup but so far that hasn't happened. I'll leave it there for now and do new posts with more requests for feedback if this one is well received.
Replies
2
Boosts
0
Views
48
Activity
3h
Deduplication of Tasks
A pretty common piece of work I'd like to generalize a Task version of is deduplicating work in callback logic by stashing the equivalent callbacks to be called when the shared underlying work is done. I have a fairly naive version of this working as it's own Task/actor/async/await soup import Foundation public actor Dupes<Key: Hashable, Success, Failure: Error> {     private var tasks = [Key:Task<Success, Failure>]() } public extension Dupes where Failure == Error {     func async(key: Key, priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success) async throws -> Success {         if let task = tasks[key] {             return try await task.value         } else {             let task = Task(priority: priority) {                 try await operation()             }             tasks[key] = task             let data = try await task.value             tasks.removeValue(forKey: key)             return data         }     } } public extension Dupes where Failure == Never {     func async(key: Key, priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> Success) async -> Success {         if let task = tasks[key] {             return await task.value         } else {             let task = Task(priority: priority) {                 await operation()             }             tasks[key] = task             let data = await task.value             tasks.removeValue(forKey: key)             return data         }     } } That can used like so if we had an async method that returns a value (bar()) based on some work that we know is shared based on the identifier 123 let foo = Foo() let dupes = Dupes<String, Int, Never>() Task.detached { await dupes.async(key: "123") {     await foo.bar() } } Task.detached { await dupes.async(key: "123") {     await foo.bar() } } In naive testing this seems to work. It feels really odd to be handing off to another actor for managing state that seems internal to whatever actor would need to avoid duplicate work. I tried defining similar methods on a custom actor or on an extension on Actor but passing the storage via keyPath or inout violates the mutability rules of actors (which to be fair makes sense). Is there an idiom for this kind of book keeping? A good real world example of this is network requests where I'd pass in a Hashable network request that makes or owns a URLRequest for me to hand off to URLSession. I don't want to download the same request twice and I don't want to write the same book keeping logic again and again.
Replies
0
Boosts
0
Views
910
Activity
Jul ’21