@@ -112,7 +112,7 @@ public extension Looper {
112112public extension Looper {
113113
114114 /// Access the underlying opaque pointer.
115- func withUnsafePointer< E, Result> ( _ body: ( OpaquePointer ) throws ( E ) -> Result ) throws ( E) -> Result where E: Error {
115+ func withUnsafePointer< E, Result> ( _ body: ( OpaquePointer ) throws ( E ) -> Result ) throws ( E) -> Result where E: Swift . Error {
116116 try body ( handle. pointer)
117117 }
118118
@@ -125,13 +125,82 @@ public extension Looper {
125125 func wake( ) {
126126 handle. wake ( )
127127 }
128+
129+ /**
130+ * Removes a previously added file descriptor from the looper.
131+ *
132+ * When this method returns, it is safe to close the file descriptor since the looper
133+ * will no longer have a reference to it. However, it is possible for the callback to
134+ * already be running or for it to run one last time if the file descriptor was already
135+ * signalled. Calling code is responsible for ensuring that this case is safely handled.
136+ * For example, if the callback takes care of removing itself during its own execution either
137+ * by returning 0 or by calling this method, then it can be guaranteed to not be invoked
138+ * again at any later time unless registered anew.
139+ *
140+ * Returns 1 if the file descriptor was removed, 0 if none was previously registered
141+ * or -1 if an error occurred.
142+ *
143+ * This method can be called on any thread.
144+ * This method may block briefly if it needs to wake the poll.
145+ */
146+ func remove( fileDescriptor: FileDescriptor ) throws ( AndroidLooperError) {
147+ try handle. remove ( fileDescriptor: fileDescriptor) . map ( fileDescriptor) . get ( )
148+ }
128149}
129150
130151// MARK: - Supporting Types
131152
132153public extension Looper {
133154
155+
156+ }
157+
158+ internal extension Looper . Handle {
159+
134160 typealias Callback = @convention ( c) ( CInt , CInt , UnsafeMutableRawPointer ? ) -> CInt
161+
162+ /// 1 if the file descriptor was removed, 0 if none was previously registered or -1 if an error occurred.
163+ enum RemoveFileDescriptorResult : CInt , Sendable , CaseIterable {
164+
165+ /// File descriptor was not previously registered.
166+ case invalid = 0
167+
168+ /// File descriptor was removed.
169+ case removed = 1
170+
171+ /// Error ocurred
172+ case error = - 1
173+ }
174+
175+ struct PollResult : Identifiable {
176+ public let id : CInt
177+ public let fd : FileDescriptor
178+ public let events : Looper . Events
179+ public let data : UnsafeRawPointer ?
180+ }
181+ }
182+
183+ internal extension Looper . Handle . RemoveFileDescriptorResult {
184+
185+ init ( _ raw: RawValue ) {
186+ guard let value = Self . init ( rawValue: raw) else {
187+ assertionFailure ( " Invalid \( Self . self) : \( raw) " )
188+ self = . error
189+ return
190+ }
191+ self = value
192+ }
193+
194+ func map( _ value: FileDescriptor ) -> Result < Void , AndroidLooperError > {
195+ switch self {
196+ case . removed:
197+ return . success( ( ) )
198+ case . invalid:
199+ return . failure( . fileDescriptorNotRegistered( value) )
200+ case . error:
201+ return . failure( . removeFileDescriptor( value) )
202+ }
203+ }
135204}
136205
137206internal extension Looper {
@@ -223,15 +292,24 @@ internal extension Looper.Handle {
223292 * This method may block briefly if it needs to wake the poll.
224293 */
225294 func add(
226- fileDescriptor fd : FileDescriptor ,
295+ fileDescriptor: FileDescriptor ,
227296 id: CInt = CInt ( ALOOPER_POLL_CALLBACK) ,
228297 events: Looper . Events = . input,
229- callback: Looper . Callback ? = nil ,
298+ callback: Callback ? = nil ,
230299 data: UnsafeMutableRawPointer ? = nil
231- ) -> Bool {
300+ ) -> Result < Void , AndroidLooperError > {
232301 let id = callback != nil ? CInt ( ALOOPER_POLL_CALLBACK) : id
233- let result = ALooper_addFd ( pointer, fd. rawValue, id, Int32 ( events. rawValue) , callback, data)
234- return result == 1
302+ let result = ALooper_addFd (
303+ pointer, fileDescriptor. rawValue,
304+ id,
305+ Int32 ( events. rawValue) ,
306+ callback,
307+ data
308+ )
309+ guard result == 1 else {
310+ return . failure( . addFileDescriptor( fileDescriptor) )
311+ }
312+ return . success( ( ) )
235313 }
236314
237315 /**
@@ -251,7 +329,74 @@ internal extension Looper.Handle {
251329 * This method can be called on any thread.
252330 * This method may block briefly if it needs to wake the poll.
253331 */
254- func remove( fileDescriptor: FileDescriptor ) -> CInt { // TODO: Create Looper.RemoveResult
255- ALooper_removeFd ( pointer, fileDescriptor. rawValue)
332+ func remove( fileDescriptor: FileDescriptor ) -> RemoveFileDescriptorResult {
333+ let result = ALooper_removeFd ( pointer, fileDescriptor. rawValue)
334+ return . init( result)
335+ }
336+
337+ /// Waits for events to be available, with optional timeout in milliseconds.
338+ @available ( macOS 13 . 0 , * )
339+ func pollOnce( duration: Duration ? = nil ) -> Result < PollResult ? , AndroidLooperError > {
340+ pollOnce ( milliseconds: duration? . milliseconds)
341+ }
342+
343+ /**
344+ * Waits for events to be available, with optional timeout in milliseconds.
345+ * Invokes callbacks for all file descriptors on which an event occurred.
346+ *
347+ * If the timeout is zero, returns immediately without blocking.
348+ * If the timeout is negative, waits indefinitely until an event appears.
349+ *
350+ * Returns ALOOPER_POLL_WAKE if the poll was awoken using ALooper_wake() before
351+ * the timeout expired and no callbacks were invoked and no other file
352+ * descriptors were ready. **All return values may also imply
353+ * ALOOPER_POLL_WAKE.**
354+ *
355+ * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked. The poll
356+ * may also have been explicitly woken by ALooper_wake.
357+ *
358+ * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given timeout
359+ * expired. The poll may also have been explicitly woken by ALooper_wake.
360+ *
361+ * Returns ALOOPER_POLL_ERROR if the calling thread has no associated Looper or
362+ * for unrecoverable internal errors. The poll may also have been explicitly
363+ * woken by ALooper_wake.
364+ *
365+ * Returns a value >= 0 containing an identifier (the same identifier `ident`
366+ * passed to ALooper_addFd()) if its file descriptor has data and it has no
367+ * callback function (requiring the caller here to handle it). In this (and
368+ * only this) case outFd, outEvents and outData will contain the poll events and
369+ * data associated with the fd, otherwise they will be set to NULL. The poll may
370+ * also have been explicitly woken by ALooper_wake.
371+ *
372+ * This method does not return until it has finished invoking the appropriate callbacks
373+ * for all file descriptors that were signalled.
374+ */
375+ func pollOnce( milliseconds: Double ? = nil ) -> Result < PollResult ? , AndroidLooperError > {
376+ var outFd : CInt = - 1
377+ var outEvents : CInt = 0
378+ var outData : UnsafeMutableRawPointer ?
379+ let timeoutMillis : CInt = milliseconds. map { CInt ( $0) } ?? 0
380+
381+ let err = ALooper_pollOnce ( timeoutMillis, & outFd, & outEvents, & outData)
382+ switch Int ( err) {
383+ case ALOOPER_POLL_WAKE:
384+ fallthrough
385+ case ALOOPER_POLL_CALLBACK:
386+ return . success( nil )
387+ case ALOOPER_POLL_TIMEOUT:
388+ return . failure( . pollTimeout)
389+ case ALOOPER_POLL_ERROR:
390+ return . failure( . pollError)
391+ default :
392+ return . success(
393+ PollResult (
394+ id: err,
395+ fd: . init( rawValue: outFd) ,
396+ events: Looper . Events ( rawValue: Int ( outEvents) ) ,
397+ data: outData
398+ )
399+ )
400+ }
256401 }
257402}
0 commit comments