Skip to content

Commit

Permalink
Promise: test and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
kosyloa committed Feb 15, 2024
1 parent 20b9934 commit cbe24cf
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 84 deletions.
103 changes: 51 additions & 52 deletions DXFeedFramework/Native/Promise/NativePromise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,15 @@
import Foundation
@_implementationOnly import graal_api

class NativePromise {
class HandlerBox<T> {
private var _value: AnyObject?
var value: T? {
return _value as? T
}
init(value: T) {
self._value = value as AnyObject
}
func resetValue() {
_value = nil
}
}
internal protocol PromiseListener: AnyObject {
func finished()
}

public typealias PromiseHandler = (_: NativePromise) -> Void
/// Native wrapper over the Java com.dxfeed.promise.Promise class.
/// The location of the imported functions is in the header files "dxfg_feed.h".
class NativePromise {
private class WeakListener: WeakBox<PromiseListener> { }
typealias PromiseHandler = (_: NativePromise) -> Void

let promise: UnsafeMutablePointer<dxfg_promise_t>?

Expand All @@ -31,22 +25,26 @@ class NativePromise {
private var result: MarketEvent?
private var results: [MarketEvent]?

private static let listeners = ConcurrentArray<HandlerBox<PromiseHandler>>()
private static let listeners = ConcurrentArray<WeakListener>()

static let listenerCallback: dxfg_promise_handler_function = { _, promise, context in
if let context = context {

let listener: AnyObject = bridge(ptr: context)
if let weakListener = listener as? HandlerBox<PromiseHandler> {
if let weakListener = listener as? WeakListener {
defer {
NativePromise.listeners.removeAll(where: {
return $0 === weakListener
})
}
guard let listener = weakListener.value else {
return
}
promise?.withMemoryRebound(to: dxfg_promise_event_t.self, capacity: 1, { pointer in
let native = NativePromise(promise: &pointer.pointee.handler)
listener(native)
})
NativePromise.listeners.removeAll(where: {
return $0 === weakListener
listener.finished()
})

}
}
}
Expand Down Expand Up @@ -103,6 +101,15 @@ class NativePromise {
return res
}

func isDone() -> Bool {
let thread = currentThread()
if let result = try? ErrorCheck.nativeCall(thread, dxfg_Promise_isDone(thread, promise)) {
return result == 1
} else {
return false
}
}

func hasResult() -> Bool {
let thread = currentThread()
if let result = try? ErrorCheck.nativeCall(thread, dxfg_Promise_hasResult(thread, promise)) {
Expand Down Expand Up @@ -142,10 +149,10 @@ class NativePromise {
return nil
}

func await() throws -> MarketEvent? {
func await() throws -> Bool {
let thread = currentThread()
try ErrorCheck.nativeCall(thread, dxfg_Promise_await(thread, promise))
return nil
let success = try ErrorCheck.nativeCall(thread, dxfg_Promise_await(thread, promise))
return success == ErrorCheck.Result.success.rawValue
}

func await(millis timeOut: Int32) throws -> Bool {
Expand All @@ -154,9 +161,10 @@ class NativePromise {
return success == ErrorCheck.Result.success.rawValue
}

func awaitWithoutException(millis timeOut: Int32) {
func awaitWithoutException(millis timeOut: Int32) -> Bool {
let thread = currentThread()
_ = try? ErrorCheck.nativeCall(thread, dxfg_Promise_awaitWithoutException(thread, promise, timeOut))
let success = try? ErrorCheck.nativeCall(thread, dxfg_Promise_awaitWithoutException(thread, promise, timeOut))
return success == ErrorCheck.Result.success.rawValue
}

func cancel() {
Expand All @@ -166,22 +174,30 @@ class NativePromise {

func complete(result: MarketEvent) throws {
let thread = currentThread()
let nativeEvent = try NativePromise.mapper.toNative(event: result)
try ErrorCheck.nativeCall(thread, dxfg_Promise_EventType_complete(thread, promise, nativeEvent))
if let nativeEvent = try NativePromise.mapper.toNative(event: result) {
defer {
NativePromise.mapper.releaseNative(native: nativeEvent)
}
try ErrorCheck.nativeCall(thread, dxfg_Promise_EventType_complete(thread, promise, nativeEvent))
}
}

func completeExceptionally(_ exception: GraalException) throws {
if let nativeException = exception.toNative() {
// defer {
// nativeException.deinitialize(count: 1)
// nativeException.deallocate()
// }
let thread = currentThread()
try ErrorCheck.nativeCall(thread, dxfg_Promise_completeExceptionally(thread, promise, nativeException))
} else {
throw ArgumentException.exception("Coudln't convert to native exception")
}
}

func whenDone(handler: @escaping NativePromise.PromiseHandler) {
func whenDone(handler: PromiseListener) {
let thread = currentThread()
let weakListener = HandlerBox(value: handler)
let weakListener = WeakListener(value: handler)
NativePromise.listeners.append(newElement: weakListener)
let voidPtr = bridge(obj: weakListener)
_ = try? ErrorCheck.nativeCall(thread,
Expand All @@ -191,29 +207,6 @@ class NativePromise {
voidPtr))
}

static func completed(result: MarketEvent) -> NativePromise? {
let thread = currentThread()
let promise = UnsafeMutablePointer<dxfg_promise_t>.allocate(capacity: 1)
if let nativeEvent = try? NativePromise.mapper.toNative(event: result) {
let handler = nativeEvent.withMemoryRebound(to: dxfg_java_object_handler.self, capacity: 1) { pointer in
return pointer
}
let result = try? ErrorCheck.nativeCall(thread, dxfg_Promise_completed(thread, promise, handler))
return NativePromise(promise: result)
}
return nil
}

static func failed(exception: GraalException) -> NativePromise? {
let thread = currentThread()
let promise = UnsafeMutablePointer<dxfg_promise_t>.allocate(capacity: 1)
if let nativeEvent = exception.toNative() {
let result = try? ErrorCheck.nativeCall(thread, dxfg_Promise_failed(thread, promise, nativeEvent))
return NativePromise(promise: result)
}
return nil
}

static func allOf(promises: [NativePromise]) throws -> NativePromise? {
let promiseList = UnsafeMutablePointer<dxfg_promise_list>.allocate(capacity: 1)
let nativeList = UnsafeMutablePointer<dxfg_java_object_handler_list>.allocate(capacity: 1)
Expand Down Expand Up @@ -243,4 +236,10 @@ class NativePromise {
let result = try ErrorCheck.nativeCall(thread, dxfg_Promises_allOf(thread, promiseList))
return NativePromise(promise: result)
}

static func removeListener(listener: PromiseListener) {
listeners.removeAll { listener in
listener.value === listener
}
}
}
110 changes: 80 additions & 30 deletions DXFeedFramework/Promise/Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,94 +7,144 @@

import Foundation

/// Result of a computation that will be completed normally or exceptionally in the future.
/// This class is designed to represent a promise to deliver certain result.
public class Promise {
public typealias PromiseHandler = (_: Promise) -> Void

private let native: NativePromise
private var handlers = [PromiseHandler]()

deinit {
NativePromise.removeListener(listener: self)
}

internal init(native: NativePromise) {
self.native = native
}

/// Returns results of computation. If computation has no result, then this method returns nil.
public func getResults() throws -> [MarketEvent]? {
return try native.getResults()
}

/// Returns results of computation. If computation has no result, then this method returns nil.
public func getResult() throws -> MarketEvent? {
return try native.getResult()
}

/// Returns **true** when computation has
/// ``complete(result:)`` completed normally,
/// or ``completeExceptionally(_:)`` exceptionally,
/// or was ``cancel()`` canceled
public func isDone() -> Bool {
return native.isDone()
}
/// Returns **true** when computation has completed normally.
/// Use ``hasResult()`` method to get the result of the computation.
public func hasResult() -> Bool {
return native.hasResult()
}

/// Returns **true** when computation has completed exceptionally or was cancelled.
/// Use ``getException()`` method to get the exceptional outcome of the computation.
public func hasException() -> Bool {
return native.hasException()
}

/// Returns **true** when computation was canceled.
/// Use ``getException()`` method to get the corresponding exception.
public func isCancelled() -> Bool {
return native.isCancelled()
}

/// Returns exceptional outcome of computation. If computation has no ``hasException()`` exception,
/// then this method returns ``nil``. If computation has completed exceptionally or was cancelled, then
/// the result of this method is not ``nil``.
/// If computation was ``isCancelled()`` cancelled, then this method returns an
// instance of GraalException.
/// - Returns: GraalException. Rethrows exception from Java.
public func getException() -> GraalException? {
return native.getException()
}


/// Wait for computation to complete and return its result or throw an exception in case of exceptional completion.
/// This method waits forever.
/// - Returns: result of computation.
/// - Throws : GraalException. Rethrows exception from Java
public func await() throws -> MarketEvent? {
return try native.await()
if try native.await() {
return try getResult()
}
return nil
}

/// Wait for computation to complete and return its result or throw an exception in case of exceptional completion.
/// If the wait times out, then the computation is ``cancel()`` cancelled and exception is thrown.
/// - Returns: result of computation.
/// - Throws : GraalException. Rethrows exception from Java
public func await(millis timeOut: Int32) throws -> MarketEvent? {
if try native.await(millis: timeOut) {
return try getResult()
}
return nil
}

public func awaitWithoutException(millis timeOut: Int32) {
/// Wait for computation to complete and return its result or throw an exception in case of exceptional completion.
/// If the wait times out, then the computation is ``cancel()`` cancelled and exception is thrown.
/// - Returns: If the wait times out, then the computation is ``cancel()`` cancelled and this method returns **false**.
/// Use this method in the code that shall continue normal execution in case of timeout.
public func awaitWithoutException(millis timeOut: Int32) -> Bool {
return native.awaitWithoutException(millis: timeOut)
}

/// Cancels computation. This method does nothing if computation has already ``isDone()`` completed.
public func cancel() {
native.cancel()
}

/// Completes computation normally with a specified result.
/// This method does nothing if computation has already ``isDone()`` completed
/// (normally, exceptionally, or was cancelled),
/// - Throws : GraalException. Rethrows exception from Java
public func complete(result: MarketEvent) throws {
try native.complete(result: result)
}

/// Completes computation exceptionally with a specified exception.
/// This method does nothing if computation has already ``isDone()`` completed,
/// otherwise ``getException()`` will return the specified exception.
/// - Throws : GraalException. Rethrows exception from Java
public func completeExceptionally(_ exception: GraalException) throws {
try native.completeExceptionally(exception)
}

/// Registers a handler to be invoked exactly once when computation ``isDone()`` completes.
/// The handler's method is invoked immediately when this computation has already completed,
/// otherwise it will be invoked **synchronously** in the future when computation
/// ``complete(result:)`` completes normally,
/// or ``completeExceptionally(_:)`` exceptionally,
/// or is ``cancel()`` cancelled from the same thread that had invoked one of the completion methods.
public func whenDone(handler: @escaping PromiseHandler) {
native.whenDone { nativePromise in
handler(Promise(native: nativePromise))
}
}

public static func completed(result: MarketEvent) -> Promise? {
if let native = NativePromise.completed(result: result) {
handlers.append(handler)
native.whenDone(handler: self)
}

/// Returns a new promise that ``isDone()`` completes when all promises from the given array
/// complete normally or exceptionally.
/// The results of the given promises are not reflected in the returned promise, but may be
/// obtained by inspecting them individually. If no promises are provided, returns a promise completed
/// with the value null.
/// When the resulting promise completes for any reason ``cancel()`` canceled, for example)
/// then all of the promises from the given array are canceled.
/// - Throws : GraalException. Rethrows exception from Java
public static func allOf(promises: [Promise]) throws -> Promise? {
if let native = try NativePromise.allOf(promises: promises.map { $0.native }) {
return Promise(native: native)
} else {
return nil
}
}

public static func failed(exception: GraalException) -> Promise? {
if let native = NativePromise.failed(exception: exception) {
return Promise(native: native)
} else {
return nil
}
}
}

public static func allOf(promises: [Promise]) throws -> Promise? {
if let native = try NativePromise.allOf(promises: promises.map { $0.native }) {
return Promise(native: native)
} else {
return nil
extension Promise: PromiseListener {
func finished() {
handlers.forEach { handler in
handler(self)
}
}

}
Loading

0 comments on commit cbe24cf

Please sign in to comment.