diff --git a/Package.swift b/Package.swift index d2417cb..bc59db7 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,6 @@ let package = Package( name: "Swiftmapper", dependencies: ["libmapper"], ), - .executableTarget(name: "Demo", dependencies: ["Swiftmapper"]), .systemLibrary( name: "libmapper", pkgConfig: "libmapper", diff --git a/Snippets/maps.swift b/Snippets/maps.swift new file mode 100644 index 0000000..79443b7 --- /dev/null +++ b/Snippets/maps.swift @@ -0,0 +1,48 @@ +import Swiftmapper + +// snippet.init +let graph = MapperGraph(); +let device = MapperDevice("MyDevice", withGraph: graph); +while !device.ready { + graph.poll(andBlockFor: 10); +} + +let signal: MapperSignal = device.createSignal("MySignal", .In); + +// snippet.searchSetup +let devices = graph.getDevices(); +var targetSignal: GenericSignal? = nil; + +// snippet.searchLoop +for dev in devices { + let signals = dev.getSignals(inDirection: .Out); + + for sig in signals { + let type = sig.getSignalType(); + let name: String = sig.getProperty(withId: .Name)!; + + if type == Float.self && name == "expr" { + targetSignal = sig; + break; + } + } + + if targetSignal != nil { + break; + } +} + +// snippet.searchEnd +if targetSignal == nil { + fatalError("Could not find a signal to map!"); +} + +// snippet.map +let map = MapperMap(from: targetSignal!, to: signal); +map.setProperty(withId: .Expression, to: "y=x+1"); +map.push(); + +while !map.ready { + graph.poll(andBlockFor: 10) +} +// map is now established \ No newline at end of file diff --git a/Sources/Demo/main.swift b/Sources/Demo/main.swift deleted file mode 100644 index 5e6369f..0000000 --- a/Sources/Demo/main.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Swiftmapper -import Foundation - -let graph = MapperGraph() -graph.subscribe(to: [.devices, .maps, .signals]) - -let device = MapperDevice("Swift Device", withGraph: graph); - -while true { - graph.poll() - if device.ready { - break; - } -} - -print("Network interface: " + graph.getInterface()) - -print("Device is ready!") - -graph.poll(andBlockFor: 100) - -for i in 0..<10 { - graph.poll(andBlockFor:10); -} - -var maps = graph.getMaps(); -print("\(maps.count) Maps:") -for map in maps { - let (srcs, dst) = map.getSignals(); - - let names: [String] = srcs.map { - $0.getProperty(withId: .Name)! - } - let dstName: String = dst.getProperty(withId: .Name)!; - - print("\t \(names) -> \(dstName)"); - - let expr: String? = map.getProperty(withId: .Expression); - print("\t\tExpression: \(expr ?? "nil")") -} - -let signal: MapperSignal<[Float]> = device.createSignal("Test_float_signal", .Out, length: 2); -let inSignal: MapperSignal = device.createSignal("Input_float", .In); - -let start = Date.now; - -while true { - graph.poll(andBlockFor: 10); - - let diff = Float(Date.now.timeIntervalSince(start)); - - //let hue = modf(Float64(diff * 180) / 360) - //device.setProperty(withName: "color.hue", to: hue.1) - //device.push(); - - signal.setValue(to: [sin(diff), cos(diff)]); - let status = inSignal.getStatus(); - if status.contains(.setRemote) { - let val = inSignal.getValue(); - print(val!); - } -} diff --git a/Sources/Swiftmapper/Docs.docc/Creating-Maps.md b/Sources/Swiftmapper/Docs.docc/Creating-Maps.md new file mode 100644 index 0000000..db22ec8 --- /dev/null +++ b/Sources/Swiftmapper/Docs.docc/Creating-Maps.md @@ -0,0 +1,49 @@ +# Creating Maps +Learn how to locate other devices and create maps with them + +## Locating Other Signals + +To create a map, you first need two signal references. Since most of the time you'll be mapping to signals that aren't in the same process, you'll have to search the graph for them. + +To do this, we'll use a ``MapperGraph`` object. We'll also be creating a device, so we can save some memory by letting the device re-use the ``MapperGraph`` for network communication by passing it to ``MapperDevice/init(_:withGraph:)`` + +@Snippet(path: "maps", slice: "init") + +> Devices created with a graph should not be polled directly, always poll the ``MapperGraph`` object instead. + +Now that we have our device and signal, we can start looking for another signal on the shared graph. In this example, we're going to map the first scalar floating point signal named "expr". + +First, since signals are owned by devices we need to search every device on the graph. We can use function ``MapperGraph/getDevices()`` to do this: + +@Snippet(path: "maps", slice: "searchSetup") + +Next, we need to loop through each device and its signals. We can skip some work by specifying we're only looking for outgoing signals in ``MapperDevice/getSignals(inDirection:)`` + +@Snippet(path: "maps", slice: "searchLoop") + +> ``GenericSignal/getSignalType()`` will return the equivalent Swift primitive metatype for the signal type, but if the signal's length is greater than one it will return the metatype of +> an array of that primitive instead. +> +> For example, if we were instead looking for a float vector signal with a length of three, the following code would be appropriate: +> ```swift +> // ... +> let length: Int = sig.getProperty(withName: .Length)!; +> if type == [Float].self && length == 3 && name == "expr" { +> // ... +> } +> ``` + +## Creating Maps + +Now that we've found our signal, we can create a map using it! + +The initializer ``MapperMap/init(from:to:)-(GenericSignal,_)`` lets you create a simple map with two endpoints. After initializing the object, you can set the expression via ``MapperObject/setProperty(withId:to:publish:)`` and publish the map using ``MapperObject/push()``. You can wait for ``MapperMap/ready`` to become true to indicate the map has become successfully established. + +@Snippet(path: "maps", slice: "map") + +### Map Ownership + +Since maps are not "owned" by any single device, this presents challenges with the automatic resource management provided by Swiftmapper's wrappers. By default any map you create locally will +be marked as owned, and will be deleted once the Swift object is deinitialized. This may not be desirable in many cases, and so you may use the method ``MapperMap/forget()`` to release ownership of the map. + +``MapperMap`` references obtained by querying the graph are unowned by default, but you can take ownership of them via ``MapperMap/take()`` if you would like their lifetime to be bound to the wrapper object. Once the wrapper's reference count reaches zero and Swift calls `deinit` the map will be removed from the graph. \ No newline at end of file diff --git a/Sources/Swiftmapper/Docs.docc/Swiftmapper.md b/Sources/Swiftmapper/Docs.docc/Swiftmapper.md index 57e3aa5..1fbe358 100644 --- a/Sources/Swiftmapper/Docs.docc/Swiftmapper.md +++ b/Sources/Swiftmapper/Docs.docc/Swiftmapper.md @@ -20,6 +20,7 @@ A library for connecting things ### Maps & Objects - +- - ``MapperMap`` - ``MapperObject`` diff --git a/Sources/Swiftmapper/Objects.swift b/Sources/Swiftmapper/Objects.swift index 1b8c835..c0d3cac 100644 --- a/Sources/Swiftmapper/Objects.swift +++ b/Sources/Swiftmapper/Objects.swift @@ -12,6 +12,7 @@ public enum MapperNamedProperty: Int32 { case Id = 0x0800 case Expression = 0x0600 case NumInstances = 0x1200 + case MapperType = 0x2400 } /// Common interface for Libmapper object wrappers @@ -62,7 +63,7 @@ extension MapperObject { /// - withId: An identifier for a specific named property /// - to: The new value /// - publish: Whether this value should be replicated to other devices - public func setProperty(withId: MapperNamedProperty, to: T, publish: Bool = true) { + public func setProperty(withId: MapperNamedProperty, to: T, publish: Bool = true) { var copy = to; copy.withUnsafeRawPointer { ptr in mpr_obj_set_prop(getHandle(), .init(UInt32(withId.rawValue)), nil, to.length(), T.asMapperType(), ptr, publish ? 1 : 0) @@ -74,7 +75,7 @@ extension MapperObject { /// - withName: The string identifier of a custom property /// - to: The new value /// - publish: Whether this value should be replicated to other devices - public func setProperty(withName: String, to: T, publish: Bool = true) { + public func setProperty(withName: String, to: T, publish: Bool = true) { var copy = to; copy.withUnsafeRawPointer { ptr in withName.withCString {str in diff --git a/Sources/Swiftmapper/Signal.swift b/Sources/Swiftmapper/Signal.swift index 8bd9065..ff5234a 100644 --- a/Sources/Swiftmapper/Signal.swift +++ b/Sources/Swiftmapper/Signal.swift @@ -1,11 +1,37 @@ import libmapper public protocol GenericSignal: MapperObject { - /// Get the signal status flags /// /// - Parameter forInstance: The instance to get flag information for, or nil for the default func getStatus(forInstance: UInt64?) -> SignalStatus + + /// Get the signal type + /// + /// If the signal has a length greater than 1, it will return the type of an array of the primitive type + func getSignalType() -> (any MappableType.Type)? +} + +public extension GenericSignal { + func getSignalType() -> (any MappableType.Type)? { + let mpr: mpr_type? = getProperty(withId: .MapperType); + if mpr == nil { + return nil; + } + let len: Int32 = getProperty(withId: .Length)!; + let vector = len > 0; + + switch Int(mpr!) { + case MPR_INT32: + return vector ? [Int32].self : Int32.self; + case MPR_DBL: + return vector ? [Double].self : Double.self; + case MPR_FLT: + return vector ? [Float].self : Float.self; + default: + return nil; + } + } } public enum MapperSignalDirection: UInt32 { diff --git a/Sources/Swiftmapper/Types.swift b/Sources/Swiftmapper/Types.swift index f31092d..e02301d 100644 --- a/Sources/Swiftmapper/Types.swift +++ b/Sources/Swiftmapper/Types.swift @@ -87,4 +87,14 @@ extension String: MapperType { public static func fromRawPointer(ptr: UnsafeRawPointer, length: Int) -> String { return String(cString: ptr.assumingMemoryBound(to: CChar.self)) } +} + +extension mpr_type: MapperType { + public mutating func withUnsafeRawPointer(body: (UnsafeRawPointer) -> ()) { + body(&self) + } + + public static func asMapperType() -> mpr_type { + return .init(UInt8(MPR_TYPE)) + } } \ No newline at end of file