diff --git a/Package.swift b/Package.swift index ddd6f22b..4220a5eb 100644 --- a/Package.swift +++ b/Package.swift @@ -12,14 +12,14 @@ if useLocalFramework { name: "FerrostarCoreRS", // IMPORTANT: Swift packages importing this locally will not be able to // import Ferrostar core unless you specify this as a relative path! - path: "./common/target/ios/ferrostar-rs.xcframework" + path: "./common/target/ios/libferrostar_core-rs.xcframework" ) } else { let releaseTag = "0.0.9" - let releaseChecksum = "96c50c1e27733e9d88cecb337aa2ee86797d2ba07461f20b76f6577a4c3c66b5" + let releaseChecksum = "318369b7304a28f45a0223759dda6fedfd9103690b3c5d5eb0144fa1adf3ac98" binaryTarget = .binaryTarget( name: "FerrostarCoreRS", - url: "https://github.com/stadiamaps/ferrostar/releases/download/\(releaseTag)/ferrostar-rs.xcframework.zip", + url: "https://github.com/stadiamaps/ferrostar/releases/download/\(releaseTag)/libferrostar_core-rs.xcframework.zip", checksum: releaseChecksum ) } diff --git a/apple/Sources/FerrostarCore/CoreLocation Extensions.swift b/apple/Sources/FerrostarCore/CoreLocation Extensions.swift index 27b406d2..45320a8d 100644 --- a/apple/Sources/FerrostarCore/CoreLocation Extensions.swift +++ b/apple/Sources/FerrostarCore/CoreLocation Extensions.swift @@ -3,7 +3,7 @@ import UniFFI extension CLLocationCoordinate2D { var geographicCoordinates: UniFFI.GeographicCoordinates { - UniFFI.GeographicCoordinates(lat: latitude, lng: longitude) + UniFFI.GeographicCoordinates(lng: longitude, lat: latitude) } init(geographicCoordinates: GeographicCoordinates) { diff --git a/apple/Sources/FerrostarCore/FerrostarCore.swift b/apple/Sources/FerrostarCore/FerrostarCore.swift index 09e1e751..ac5a972f 100644 --- a/apple/Sources/FerrostarCore/FerrostarCore.swift +++ b/apple/Sources/FerrostarCore/FerrostarCore.swift @@ -105,8 +105,7 @@ public protocol FerrostarCoreDelegate: AnyObject { if let res = response as? HTTPURLResponse, res.statusCode < 200 || res.statusCode >= 300 { throw FerrostarCoreError.httpStatusCode(res.statusCode) } else { - let uint8Data = [UInt8](data) - let routes = try routeAdapter.parseResponse(response: uint8Data) + let routes = try routeAdapter.parseResponse(response: data) return routes.map { Route(inner: $0) } } diff --git a/apple/Sources/FerrostarMapLibreUI/BannerView.swift b/apple/Sources/FerrostarMapLibreUI/BannerView.swift index be069a7a..095a1bd5 100644 --- a/apple/Sources/FerrostarMapLibreUI/BannerView.swift +++ b/apple/Sources/FerrostarMapLibreUI/BannerView.swift @@ -80,7 +80,7 @@ struct BannerView: View { } #Preview { - let location = GeographicCoordinates(lat: 0, lng: 0) + let location = GeographicCoordinates(lng: 0, lat: 0) let instructions = UniFFI.VisualInstructions(primaryContent: VisualInstructionContent(text: "Hyde Street", maneuverType: .turn, maneuverModifier: .left, roundaboutExitDegrees: nil), secondaryContent: nil, triggerDistanceBeforeManeuver: 42.0) return BannerView(instructions: instructions, distanceToNextManeuver: 42) diff --git a/apple/Sources/UniFFI/ferrostar.swift b/apple/Sources/UniFFI/ferrostar_core.swift similarity index 96% rename from apple/Sources/UniFFI/ferrostar.swift rename to apple/Sources/UniFFI/ferrostar_core.swift index b49c7483..a2d07761 100644 --- a/apple/Sources/UniFFI/ferrostar.swift +++ b/apple/Sources/UniFFI/ferrostar_core.swift @@ -5,8 +5,8 @@ import Foundation // Depending on the consumer's build setup, the low-level FFI code // might be in a separate module, or it might be compiled inline into // this module. This is a bit of light hackery to work with both. -#if canImport(ferrostarFFI) - import ferrostarFFI +#if canImport(ferrostar_coreFFI) + import ferrostar_coreFFI #endif private extension RustBuffer { @@ -298,19 +298,6 @@ private func uniffiCheckCallStatus( // Public interface members begin here. -private struct FfiConverterUInt8: FfiConverterPrimitive { - typealias FfiType = UInt8 - typealias SwiftType = UInt8 - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { - return try lift(readInt(&buf)) - } - - public static func write(_ value: UInt8, into buf: inout [UInt8]) { - writeInt(&buf, lower(value)) - } -} - private struct FfiConverterUInt16: FfiConverterPrimitive { typealias FfiType = UInt16 typealias SwiftType = UInt16 @@ -388,6 +375,21 @@ private struct FfiConverterString: FfiConverter { } } +private struct FfiConverterData: FfiConverterRustBuffer { + typealias SwiftType = Data + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Data { + let len: Int32 = try readInt(&buf) + return try Data(readBytes(&buf, count: Int(len))) + } + + public static func write(_ value: Data, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + writeBytes(&buf, value) + } +} + private struct FfiConverterTimestamp: FfiConverterRustBuffer { typealias SwiftType = Date @@ -512,7 +514,7 @@ public func FfiConverterTypeNavigationController_lower(_ value: NavigationContro public protocol RouteAdapterProtocol { func generateRequest(userLocation: UserLocation, waypoints: [GeographicCoordinates]) throws -> RouteRequest - func parseResponse(response: [UInt8]) throws -> [Route] + func parseResponse(response: Data) throws -> [Route] } public class RouteAdapter: RouteAdapterProtocol { @@ -557,11 +559,11 @@ public class RouteAdapter: RouteAdapterProtocol { ) } - public func parseResponse(response: [UInt8]) throws -> [Route] { + public func parseResponse(response: Data) throws -> [Route] { return try FfiConverterSequenceTypeRoute.lift( rustCallWithError(FfiConverterTypeRoutingResponseParseError.lift) { uniffi_ferrostar_core_fn_method_routeadapter_parse_response(self.pointer, - FfiConverterSequenceUInt8.lower(response), $0) + FfiConverterData.lower(response), $0) } ) } @@ -673,7 +675,7 @@ public func FfiConverterTypeRouteRequestGenerator_lower(_ value: RouteRequestGen } public protocol RouteResponseParserProtocol { - func parseResponse(response: [UInt8]) throws -> [Route] + func parseResponse(response: Data) throws -> [Route] } public class RouteResponseParser: RouteResponseParserProtocol { @@ -690,11 +692,11 @@ public class RouteResponseParser: RouteResponseParserProtocol { try! rustCall { uniffi_ferrostar_core_fn_free_routeresponseparser(pointer, $0) } } - public func parseResponse(response: [UInt8]) throws -> [Route] { + public func parseResponse(response: Data) throws -> [Route] { return try FfiConverterSequenceTypeRoute.lift( rustCallWithError(FfiConverterTypeRoutingResponseParseError.lift) { uniffi_ferrostar_core_fn_method_routeresponseparser_parse_response(self.pointer, - FfiConverterSequenceUInt8.lower(response), $0) + FfiConverterData.lower(response), $0) } ) } @@ -790,45 +792,45 @@ public func FfiConverterTypeCourseOverGround_lower(_ value: CourseOverGround) -> } public struct GeographicCoordinates { - public var lat: Double public var lng: Double + public var lat: Double // Default memberwise initializers are never public by default, so we // declare one manually. - public init(lat: Double, lng: Double) { - self.lat = lat + public init(lng: Double, lat: Double) { self.lng = lng + self.lat = lat } } extension GeographicCoordinates: Equatable, Hashable { public static func == (lhs: GeographicCoordinates, rhs: GeographicCoordinates) -> Bool { - if lhs.lat != rhs.lat { + if lhs.lng != rhs.lng { return false } - if lhs.lng != rhs.lng { + if lhs.lat != rhs.lat { return false } return true } public func hash(into hasher: inout Hasher) { - hasher.combine(lat) hasher.combine(lng) + hasher.combine(lat) } } public struct FfiConverterTypeGeographicCoordinates: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> GeographicCoordinates { return try GeographicCoordinates( - lat: FfiConverterDouble.read(from: &buf), - lng: FfiConverterDouble.read(from: &buf) + lng: FfiConverterDouble.read(from: &buf), + lat: FfiConverterDouble.read(from: &buf) ) } public static func write(_ value: GeographicCoordinates, into buf: inout [UInt8]) { - FfiConverterDouble.write(value.lat, into: &buf) FfiConverterDouble.write(value.lng, into: &buf) + FfiConverterDouble.write(value.lat, into: &buf) } } @@ -1535,7 +1537,7 @@ extension NavigationStateUpdate: Equatable, Hashable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. public enum RouteRequest { - case httpPost(url: String, headers: [String: String], body: [UInt8]) + case httpPost(url: String, headers: [String: String], body: Data) } public struct FfiConverterTypeRouteRequest: FfiConverterRustBuffer { @@ -1547,7 +1549,7 @@ public struct FfiConverterTypeRouteRequest: FfiConverterRustBuffer { case 1: return try .httpPost( url: FfiConverterString.read(from: &buf), headers: FfiConverterDictionaryStringString.read(from: &buf), - body: FfiConverterSequenceUInt8.read(from: &buf) + body: FfiConverterData.read(from: &buf) ) default: throw UniffiInternalError.unexpectedEnumCase @@ -1560,7 +1562,7 @@ public struct FfiConverterTypeRouteRequest: FfiConverterRustBuffer { writeInt(&buf, Int32(1)) FfiConverterString.write(url, into: &buf) FfiConverterDictionaryStringString.write(headers, into: &buf) - FfiConverterSequenceUInt8.write(body, into: &buf) + FfiConverterData.write(body, into: &buf) } } } @@ -1576,14 +1578,9 @@ public func FfiConverterTypeRouteRequest_lower(_ value: RouteRequest) -> RustBuf extension RouteRequest: Equatable, Hashable {} public enum RoutingRequestGenerationError { - // Simple error enums only carry a message - case NotEnoughWaypoints(message: String) - - // Simple error enums only carry a message - case JsonError(message: String) - - // Simple error enums only carry a message - case UnknownError(message: String) + case NotEnoughWaypoints + case JsonError + case UnknownError fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { return try FfiConverterTypeRoutingRequestGenerationError.lift(error) @@ -1596,17 +1593,9 @@ public struct FfiConverterTypeRoutingRequestGenerationError: FfiConverterRustBuf public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RoutingRequestGenerationError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return try .NotEnoughWaypoints( - message: FfiConverterString.read(from: &buf) - ) - - case 2: return try .JsonError( - message: FfiConverterString.read(from: &buf) - ) - - case 3: return try .UnknownError( - message: FfiConverterString.read(from: &buf) - ) + case 1: return .NotEnoughWaypoints + case 2: return .JsonError + case 3: return .UnknownError default: throw UniffiInternalError.unexpectedEnumCase } @@ -1614,11 +1603,13 @@ public struct FfiConverterTypeRoutingRequestGenerationError: FfiConverterRustBuf public static func write(_ value: RoutingRequestGenerationError, into buf: inout [UInt8]) { switch value { - case .NotEnoughWaypoints(_ /* message is ignored*/ ): + case .NotEnoughWaypoints: writeInt(&buf, Int32(1)) - case .JsonError(_ /* message is ignored*/ ): + + case .JsonError: writeInt(&buf, Int32(2)) - case .UnknownError(_ /* message is ignored*/ ): + + case .UnknownError: writeInt(&buf, Int32(3)) } } @@ -1852,28 +1843,6 @@ private struct FfiConverterOptionTypeManeuverType: FfiConverterRustBuffer { } } -private struct FfiConverterSequenceUInt8: FfiConverterRustBuffer { - typealias SwiftType = [UInt8] - - public static func write(_ value: [UInt8], into buf: inout [UInt8]) { - let len = Int32(value.count) - writeInt(&buf, len) - for item in value { - FfiConverterUInt8.write(item, into: &buf) - } - } - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [UInt8] { - let len: Int32 = try readInt(&buf) - var seq = [UInt8]() - seq.reserveCapacity(Int(len)) - for _ in 0 ..< len { - try seq.append(FfiConverterUInt8.read(from: &buf)) - } - return seq - } -} - private struct FfiConverterSequenceTypeGeographicCoordinates: FfiConverterRustBuffer { typealias SwiftType = [GeographicCoordinates] @@ -2022,37 +1991,37 @@ private var initializationResult: InitializationResult { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if uniffi_ferrostar_core_checksum_func_create_osrm_response_parser() != 50895 { + if uniffi_ferrostar_core_checksum_func_create_osrm_response_parser() != 13999 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_func_create_valhalla_request_generator() != 62930 { + if uniffi_ferrostar_core_checksum_func_create_valhalla_request_generator() != 29123 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_method_navigationcontroller_advance_to_next_step() != 53731 { + if uniffi_ferrostar_core_checksum_method_navigationcontroller_advance_to_next_step() != 1041 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_method_navigationcontroller_update_user_location() != 4353 { + if uniffi_ferrostar_core_checksum_method_navigationcontroller_update_user_location() != 64701 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_method_routeadapter_generate_request() != 46269 { + if uniffi_ferrostar_core_checksum_method_routeadapter_generate_request() != 61210 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_method_routeadapter_parse_response() != 11562 { + if uniffi_ferrostar_core_checksum_method_routeadapter_parse_response() != 49628 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_method_routerequestgenerator_generate_request() != 65091 { + if uniffi_ferrostar_core_checksum_method_routerequestgenerator_generate_request() != 10361 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_method_routeresponseparser_parse_response() != 27004 { + if uniffi_ferrostar_core_checksum_method_routeresponseparser_parse_response() != 54334 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_constructor_navigationcontroller_new() != 55587 { + if uniffi_ferrostar_core_checksum_constructor_navigationcontroller_new() != 12876 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_constructor_routeadapter_new() != 17242 { + if uniffi_ferrostar_core_checksum_constructor_routeadapter_new() != 32286 { return InitializationResult.apiChecksumMismatch } - if uniffi_ferrostar_core_checksum_constructor_routeadapter_new_valhalla_http() != 148 { + if uniffi_ferrostar_core_checksum_constructor_routeadapter_new_valhalla_http() != 17926 { return InitializationResult.apiChecksumMismatch } diff --git a/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift b/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift index 0e091963..67b24690 100644 --- a/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift +++ b/apple/Tests/FerrostarCoreTests/FerrostarCoreTests.swift @@ -20,10 +20,10 @@ private class MockRouteAdapter: RouteAdapterProtocol { } func generateRequest(userLocation: UniFFI.UserLocation, waypoints: [UniFFI.GeographicCoordinates]) throws -> UniFFI.RouteRequest { - return UniFFI.RouteRequest.httpPost(url: backendUrl.absoluteString, headers: [:], body: []) + return UniFFI.RouteRequest.httpPost(url: backendUrl.absoluteString, headers: [:], body: Data()) } - func parseResponse(response _: [UInt8]) throws -> [UniFFI.Route] { + func parseResponse(response _: Data) throws -> [UniFFI.Route] { return routes } } diff --git a/common/build-ios.sh b/common/build-ios.sh index 656ffd74..1e7870cc 100755 --- a/common/build-ios.sh +++ b/common/build-ios.sh @@ -32,15 +32,15 @@ fat_simulator_lib_dir="target/ios-simulator-fat/release" generate_ffi() { echo "Generating framework module mapping and FFI bindings" - cargo run -p uniffi-bindgen generate $1/$2.udl --language swift --out-dir target/uniffi-xcframework-staging-$2 - mv target/uniffi-xcframework-staging-$2/*.swift ../apple/Sources/UniFFI/ - mv target/uniffi-xcframework-staging-$2/$2FFI.modulemap target/uniffi-xcframework-staging-$2/module.modulemap # Convention requires this have a specific name + cargo run -p uniffi-bindgen generate --library target/aarch64-apple-ios/release/lib$1_core.dylib --language swift --out-dir target/uniffi-xcframework-staging + mv target/uniffi-xcframework-staging/*.swift ../apple/Sources/UniFFI/ + mv target/uniffi-xcframework-staging/$1_coreFFI.modulemap target/uniffi-xcframework-staging/module.modulemap # Convention requires this have a specific name } create_fat_simulator_lib() { echo "Creating a fat library for x86_64 and aarch64 simulators" mkdir -p $fat_simulator_lib_dir - lipo -create target/x86_64-apple-ios/release/$1.a target/aarch64-apple-ios-sim/release/$1.a -output $fat_simulator_lib_dir/$1.a + lipo -create target/x86_64-apple-ios/release/lib$1_core.a target/aarch64-apple-ios-sim/release/lib$1_core.a -output $fat_simulator_lib_dir/lib$1_core.a } build_xcframework() { @@ -48,14 +48,14 @@ build_xcframework() { echo "Generating XCFramework" rm -rf target/ios # Delete the output folder so we can regenerate it xcodebuild -create-xcframework \ - -library target/aarch64-apple-ios/release/$1.a -headers target/uniffi-xcframework-staging-$2 \ - -library target/ios-simulator-fat/release/$1.a -headers target/uniffi-xcframework-staging-$2 \ - -output target/ios/$2-rs.xcframework + -library target/aarch64-apple-ios/release/lib$1_core.a -headers target/uniffi-xcframework-staging \ + -library target/ios-simulator-fat/release/lib$1_core.a -headers target/uniffi-xcframework-staging \ + -output target/ios/lib$1_core-rs.xcframework if $release; then echo "Building xcframework archive" - zip -r target/ios/$2-rs.xcframework.zip target/ios/$2-rs.xcframework - checksum=$(swift package compute-checksum target/ios/$2-rs.xcframework.zip) + zip -r target/ios/lib$1_core-rs.xcframework.zip target/ios/lib$1_core-rs.xcframework + checksum=$(swift package compute-checksum target/ios/lib$1_core-rs.xcframework.zip) version=$(cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="ferrostar-core") .version') sed -i "" -E "s/(let releaseTag = \")[^\"]+(\")/\1$version\2/g" ../Package.swift sed -i "" -E "s/(let releaseChecksum = \")[^\"]+(\")/\1$checksum\2/g" ../Package.swift @@ -66,6 +66,7 @@ cargo build --lib --release --target x86_64-apple-ios cargo build --lib --release --target aarch64-apple-ios-sim cargo build --lib --release --target aarch64-apple-ios -generate_ffi ferrostar-core/src ferrostar -create_fat_simulator_lib libferrostar_core -build_xcframework libferrostar_core ferrostar +basename=ferrostar +generate_ffi $basename +create_fat_simulator_lib $basename +build_xcframework $basename diff --git a/common/ferrostar-core/build.rs b/common/ferrostar-core/build.rs deleted file mode 100644 index 1af34c8f..00000000 --- a/common/ferrostar-core/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - uniffi::generate_scaffolding("./src/ferrostar.udl").unwrap(); -} diff --git a/common/ferrostar-core/src/ferrostar.udl b/common/ferrostar-core/src/ferrostar.udl deleted file mode 100644 index 3caa6a33..00000000 --- a/common/ferrostar-core/src/ferrostar.udl +++ /dev/null @@ -1,171 +0,0 @@ -// -// Namespace to encapsulate top-level functions -// - -namespace ferrostar { - RouteRequestGenerator create_valhalla_request_generator(string endpoint_url, string profile); - RouteResponseParser create_osrm_response_parser(u32 polyline_precision); -}; - -// -// Error types -// - -[Error] -enum RoutingRequestGenerationError { - "NotEnoughWaypoints", - "JsonError", - "UnknownError" -}; - -[Error] -interface RoutingResponseParseError { - ParseError(string error); - UnknownError(); -}; - -// -// Data structures -// - -dictionary GeographicCoordinates { - double lat; - double lng; -}; - -dictionary CourseOverGround { - u16 degrees; - u16 accuracy; -}; - -dictionary UserLocation { - GeographicCoordinates coordinates; - double horizontal_accuracy; - CourseOverGround? course_over_ground; - timestamp timestamp; -}; - -dictionary Route { - sequence geometry; - double distance; - sequence waypoints; - sequence steps; -}; - -dictionary RouteStep { - sequence geometry; - double distance; - string? road_name; - string instruction; - sequence visual_instructions; -}; - -dictionary SpokenInstruction { - string text; - string? ssml; - f64 trigger_distance_before_maneuver; -}; - -dictionary VisualInstructions { - VisualInstructionContent primary_content; - VisualInstructionContent? secondary_content; - f64 trigger_distance_before_maneuver; -}; - -dictionary VisualInstructionContent { - string text; - ManeuverType? maneuver_type; - ManeuverModifier? maneuver_modifier; - u16? roundabout_exit_degrees; -}; - -enum ManeuverType { - "Turn", - "NewName", - "Depart", - "Arrive", - "Merge", - "OnRamp", - "OffRamp", - "Fork", - "EndOfRoad", - "Continue", - "Roundabout", - "Rotary", - "RoundaboutTurn", - "Notification", - "ExitRoundabout", - "ExitRotary", -}; - -enum ManeuverModifier { - "UTurn", - "SharpRight", - "Right", - "SlightRight", - "Straight", - "SlightLeft", - "Left", - "SharpLeft", -}; - -[Enum] -interface RouteRequest { - HttpPost(string url, record headers, sequence body); -}; - -[Enum] -interface NavigationStateUpdate { - Navigating(UserLocation snapped_user_location, sequence remaining_waypoints, RouteStep current_step, double distance_to_next_maneuver); - Arrived(); -}; - -[Enum] -interface StepAdvanceMode { - Manual(); - DistanceToEndOfStep(u16 distance, u16 minimum_horizontal_accuracy); - RelativeLineStringDistance(u16 minimum_horizontal_accuracy, u16? automatic_advance_distance); -}; - -dictionary NavigationControllerConfig { - StepAdvanceMode step_advance; -}; - -// -// Callback interfaces (foreign code can implement these to extend functionality) -// - -[Trait] -interface RouteRequestGenerator { - [Throws=RoutingRequestGenerationError] - RouteRequest generate_request(UserLocation user_location, sequence waypoints); -}; - -[Trait] -interface RouteResponseParser { - [Throws=RoutingResponseParseError] - sequence parse_response(sequence response); -}; - -// -// Concrete core objects -// - -interface RouteAdapter { - constructor(RouteRequestGenerator request_generator, RouteResponseParser response_parser); - [Name=new_valhalla_http] - constructor(string endpoint_url, string profile); - - [Throws=RoutingRequestGenerationError] - RouteRequest generate_request(UserLocation user_location, sequence waypoints); - - [Throws=RoutingResponseParseError] - sequence parse_response(sequence response); -}; - -interface NavigationController { - constructor(UserLocation last_user_location, Route route, NavigationControllerConfig config); - - NavigationStateUpdate advance_to_next_step(); - NavigationStateUpdate update_user_location(UserLocation location); -}; \ No newline at end of file diff --git a/common/ferrostar-core/src/lib.rs b/common/ferrostar-core/src/lib.rs index 39c9746a..2151c52d 100644 --- a/common/ferrostar-core/src/lib.rs +++ b/common/ferrostar-core/src/lib.rs @@ -17,6 +17,8 @@ pub use routing_adapters::{ RouteAdapter, RouteRequest, RouteRequestGenerator, RouteResponseParser, }; +uniffi::setup_scaffolding!(); + // // Helpers that are only exposed via the FFI interface. // @@ -25,6 +27,7 @@ pub use routing_adapters::{ // Instead we use top-level functions to return dynamic objects conforming to the trait. // +#[uniffi::export] fn create_valhalla_request_generator( endpoint_url: String, profile: String, @@ -32,8 +35,7 @@ fn create_valhalla_request_generator( Arc::new(ValhallaHttpRequestGenerator::new(endpoint_url, profile)) } +#[uniffi::export] fn create_osrm_response_parser(polyline_precision: u32) -> Arc { Arc::new(OsrmResponseParser::new(polyline_precision)) } - -uniffi::include_scaffolding!("ferrostar"); diff --git a/common/ferrostar-core/src/models.rs b/common/ferrostar-core/src/models.rs index a2aa54b6..17b6ba3a 100644 --- a/common/ferrostar-core/src/models.rs +++ b/common/ferrostar-core/src/models.rs @@ -5,7 +5,7 @@ use std::time::SystemTime; #[cfg(test)] use serde::Serialize; -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] pub struct GeographicCoordinates { pub lng: f64, @@ -37,7 +37,7 @@ impl From for Point { } /// The direction in which the user/device is observed to be traveling. -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, uniffi::Record)] pub struct CourseOverGround { /// The direction in which the user's device is traveling, measured in clockwise degrees from /// true north (N = 0, E = 90, S = 180, W = 270). @@ -56,7 +56,7 @@ impl CourseOverGround { /// /// In addition to coordinates, this includes estimated accuracy and course information, /// which can influence navigation logic and UI. -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, uniffi::Record)] pub struct UserLocation { pub coordinates: GeographicCoordinates, /// The estimated accuracy of the coordinate (in meters) @@ -76,7 +76,7 @@ impl From for Point { /// /// NOTE: This type is unstable and is still under active development and should be /// considered unstable. -#[derive(Debug)] +#[derive(Debug, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] pub struct Route { pub geometry: Vec, @@ -95,7 +95,7 @@ pub struct Route { /// NOTE: OSRM specifies this rather precisely as "travel along a single way to the subsequent step" /// but we will intentionally define this somewhat looser unless/until it becomes clear something /// -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] pub struct RouteStep { pub geometry: Vec, @@ -118,7 +118,7 @@ impl RouteStep { // TODO: trigger_at doesn't really have to live in the public interface; figure out if we want to have a separate FFI vs internal type -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, uniffi::Record)] pub struct SpokenInstruction { /// Plain-text instruction which can be synthesized with a TTS engine. pub text: String, @@ -128,19 +128,10 @@ pub struct SpokenInstruction { pub trigger_distance_before_maneuver: f64, } -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(test, derive(Serialize))] -pub struct VisualInstructions { - pub primary_content: VisualInstructionContent, - pub secondary_content: Option, - /// How far (in meters) from the upcoming maneuver the instruction should start being displayed - pub trigger_distance_before_maneuver: f64, -} - /// Indicates the type of maneuver to perform. /// /// Frequently used in conjunction with [ManeuverModifier]. -#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, uniffi::Enum)] #[cfg_attr(test, derive(Serialize))] #[serde(rename_all = "lowercase")] pub enum ManeuverType { @@ -170,7 +161,7 @@ pub enum ManeuverType { } /// Specifies additional information about a [ManeuverType] -#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Deserialize, Debug, Copy, Clone, Eq, PartialEq, uniffi::Enum)] #[cfg_attr(test, derive(Serialize))] #[serde(rename_all = "lowercase")] pub enum ManeuverModifier { @@ -188,7 +179,7 @@ pub enum ManeuverModifier { SharpLeft, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, uniffi::Record)] #[cfg_attr(test, derive(Serialize))] pub struct VisualInstructionContent { pub text: String, @@ -196,3 +187,12 @@ pub struct VisualInstructionContent { pub maneuver_modifier: Option, pub roundabout_exit_degrees: Option, } + +#[derive(Debug, Clone, PartialEq, uniffi::Record)] +#[cfg_attr(test, derive(Serialize))] +pub struct VisualInstructions { + pub primary_content: VisualInstructionContent, + pub secondary_content: Option, + /// How far (in meters) from the upcoming maneuver the instruction should start being displayed + pub trigger_distance_before_maneuver: f64, +} diff --git a/common/ferrostar-core/src/navigation_controller/mod.rs b/common/ferrostar-core/src/navigation_controller/mod.rs index bfe0107f..842f9827 100644 --- a/common/ferrostar-core/src/navigation_controller/mod.rs +++ b/common/ferrostar-core/src/navigation_controller/mod.rs @@ -8,7 +8,7 @@ use crate::navigation_controller::algorithms::{ use algorithms::snap_user_location_to_line; use geo::Coord; use models::*; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; /// Manages the navigation lifecycle of a single trip, requesting the initial route and updating /// internal state based on inputs like user location updates. @@ -25,6 +25,7 @@ use std::sync::Mutex; /// /// This is intentionally impossible to construct without a user location, so tasks like /// waiting for a fix (or determining if a cached fix is good enough), are left to higher levels. +#[derive(uniffi::Object)] pub struct NavigationController { /// The last known location of the user. For all intents and purposes, the "user" is assumed /// to be at the location reported by their device (phone, car, etc.) @@ -36,13 +37,15 @@ pub struct NavigationController { config: NavigationControllerConfig, } +#[uniffi::export] impl NavigationController { /// Creates a new trip navigation controller given the user's last known location and a route. + #[uniffi::constructor] pub fn new( last_user_location: UserLocation, route: Route, config: NavigationControllerConfig, - ) -> Self { + ) -> Arc { let remaining_waypoints = route.waypoints.clone(); let remaining_steps = route.steps.clone(); let route_linestring = route @@ -53,15 +56,15 @@ impl NavigationController { let Some(current_route_step) = remaining_steps.first() else { // Bail early; if we don't have any steps, this is a useless route - return Self { + return Arc::new(Self { state: Mutex::new(TripState::Complete), config, - }; + }); }; let current_step_linestring = current_route_step.get_linestring(); - Self { + Arc::new(Self { state: Mutex::new(TripState::Navigating { last_user_location, snapped_user_location: snap_user_location_to_line( @@ -75,7 +78,7 @@ impl NavigationController { current_step_linestring, }), config, - } + }) } /// Advances navigation to the next step. diff --git a/common/ferrostar-core/src/navigation_controller/models.rs b/common/ferrostar-core/src/navigation_controller/models.rs index 6cedb63f..104b9846 100644 --- a/common/ferrostar-core/src/navigation_controller/models.rs +++ b/common/ferrostar-core/src/navigation_controller/models.rs @@ -25,7 +25,7 @@ pub(super) enum TripState { } /// Public updates pushed up to the direct user of the NavigationController. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, uniffi::Enum)] pub enum NavigationStateUpdate { Navigating { snapped_user_location: UserLocation, @@ -50,7 +50,7 @@ pub enum StepAdvanceStatus { EndOfRoute, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, uniffi::Enum)] pub enum StepAdvanceMode { /// Never advances to the next step automatically Manual, @@ -74,7 +74,7 @@ pub enum StepAdvanceMode { }, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, uniffi::Record)] pub struct NavigationControllerConfig { pub step_advance: StepAdvanceMode, } diff --git a/common/ferrostar-core/src/routing_adapters/error.rs b/common/ferrostar-core/src/routing_adapters/error.rs index 2c4760db..c7020830 100644 --- a/common/ferrostar-core/src/routing_adapters/error.rs +++ b/common/ferrostar-core/src/routing_adapters/error.rs @@ -3,7 +3,7 @@ use uniffi::UnexpectedUniFFICallbackError; // TODO: This implementation seems less than ideal. In particular, it hides what sort of JSON error occurred due to an apparent bug in UniFFI. // The trouble appears to be with generating "flat" enum bindings that are used with callback // interfaces when the underlying actually has fields. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum RoutingRequestGenerationError { #[error("Too few waypoints were provided to compute a route.")] NotEnoughWaypoints, @@ -25,7 +25,7 @@ impl From for RoutingRequestGenerationError { } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error)] pub enum RoutingResponseParseError { // TODO: Unable to find route and other common errors #[error("Failed to parse route response: {error}.")] diff --git a/common/ferrostar-core/src/routing_adapters/mod.rs b/common/ferrostar-core/src/routing_adapters/mod.rs index fdb0fae8..ccc375b2 100644 --- a/common/ferrostar-core/src/routing_adapters/mod.rs +++ b/common/ferrostar-core/src/routing_adapters/mod.rs @@ -12,7 +12,7 @@ pub mod osrm; pub mod valhalla; /// A route request generated by a [RouteRequestGenerator]. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, uniffi::Enum)] pub enum RouteRequest { HttpPost { url: String, @@ -32,6 +32,7 @@ pub enum RouteRequest { /// /// Implementations may be either in Rust (most popular engines should eventually have Rust /// implementations) or foreign code. +#[uniffi::export] pub trait RouteRequestGenerator: Send + Sync + Debug { /// Generates a routing backend request given the set of locations. /// @@ -49,6 +50,7 @@ pub trait RouteRequestGenerator: Send + Sync + Debug { /// A generic interface describing any object capable of parsing a response from a routing /// backend into one or more [Route]s. +#[uniffi::export] pub trait RouteResponseParser: Send + Sync + Debug { /// Parses a raw response from the routing backend into a route. /// @@ -73,23 +75,27 @@ pub trait RouteResponseParser: Send + Sync + Debug { /// I don't think we can do this in the type system, since one of the reasons for the split design /// is modularity, including the possibility of user-provided implementations, and these will not /// always be of a "known" type to the Rust side. +#[derive(uniffi::Object)] pub struct RouteAdapter { request_generator: Arc, response_parser: Arc, } +#[uniffi::export] impl RouteAdapter { + #[uniffi::constructor] pub fn new( request_generator: Arc, response_parser: Arc, - ) -> Self { - Self { + ) -> Arc { + Arc::new(Self { request_generator, response_parser, - } + }) } - pub fn new_valhalla_http(endpoint_url: String, profile: String) -> Self { + #[uniffi::constructor] + pub fn new_valhalla_http(endpoint_url: String, profile: String) -> Arc { let request_generator = create_valhalla_request_generator(endpoint_url, profile); let response_parser = create_osrm_response_parser(6); Self::new(request_generator, response_parser)