Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support unordered_map<K, V> (and fix arrays bidirectionally) #28

Merged
merged 3 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/nitrogen/src/autolinking/createSwiftCxxBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { SourceFile } from '../syntax/SourceFile.js'
import { SwiftCxxBridgedType } from '../syntax/swift/SwiftCxxBridgedType.js'
import { indent } from '../utils.js'

const SWIFT_BRIDGE_NAMESPACE = ['bridge', 'swift']

export function createSwiftCxxBridge(): SourceFile[] {
const moduleName = NitroConfig.getIosModuleName()
const bridgeName = `${moduleName}-Swift-Cxx-Bridge`
Expand All @@ -28,7 +30,10 @@ export function createSwiftCxxBridge(): SourceFile[] {
.map((i) => i.forwardDeclaration)
.filter((f) => f != null)
.filter(isNotDuplicate)
const namespace = NitroConfig.getCxxNamespace('c++')
const namespace = NitroConfig.getCxxNamespace(
'c++',
...SWIFT_BRIDGE_NAMESPACE
)

const header = `
${createFileMetadataString(`${bridgeName}.hpp`)}
Expand All @@ -41,6 +46,10 @@ ${forwardDeclarations.sort().join('\n')}
// Include C++ defined types
${includes.sort().join('\n')}

/**
* Contains specialized versions of C++ templated types so they can be accessed from Swift,
* as well as helper functions to interact with those C++ types from Swift.
*/
namespace ${namespace} {

${indent(helperFunctions, ' ')}
Expand Down
62 changes: 59 additions & 3 deletions packages/nitrogen/src/syntax/swift/SwiftCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { FunctionType } from '../types/FunctionType.js'
import { getTypeAs } from '../types/getTypeAs.js'
import { HybridObjectType } from '../types/HybridObjectType.js'
import { OptionalType } from '../types/OptionalType.js'
import { RecordType } from '../types/RecordType.js'
import type { Type } from '../types/Type.js'
import {
createSwiftCxxHelpers,
Expand Down Expand Up @@ -51,6 +52,9 @@ export class SwiftCxxBridgedType {
case 'array':
// swift::Array<T> <> std::vector<T>
return true
case 'record':
// Dictionary<K, V> <> std::unordered_map<K, V>
return true
case 'function':
// (@ecaping () -> Void) <> std::function<...>
return true
Expand Down Expand Up @@ -129,7 +133,7 @@ export class SwiftCxxBridgedType {
case 'function':
case 'record': {
const bridge = this.getBridgeOrThrow()
return NitroConfig.getCxxNamespace(language, bridge.specializationName)
return `bridge.${bridge.specializationName}`
}
case 'string': {
switch (language) {
Expand Down Expand Up @@ -202,6 +206,38 @@ export class SwiftCxxBridgedType {
return cppParameterName
}
}
case 'array': {
const array = getTypeAs(this.type, ArrayType)
const wrapping = new SwiftCxxBridgedType(array.itemType)
switch (language) {
case 'swift':
return `${cppParameterName}.map({ val in ${wrapping.parseFromCppToSwift('val', 'swift')} })`.trim()
default:
return cppParameterName
}
}
case 'record': {
const bridge = this.getBridgeOrThrow()
const getKeysFunc = `bridge.get_${bridge.specializationName}_keys`
const record = getTypeAs(this.type, RecordType)
const wrappingKey = new SwiftCxxBridgedType(record.keyType)
const wrappingValue = new SwiftCxxBridgedType(record.valueType)
switch (language) {
case 'swift':
return `
{
var dictionary = ${record.getCode('swift')}(minimumCapacity: ${cppParameterName}.size())
let keys = ${getKeysFunc}(${cppParameterName})
for key in keys {
let value = ${cppParameterName}[key]
dictionary[${wrappingKey.parseFromCppToSwift('key', 'swift')}] = ${wrappingValue.parseFromCppToSwift('value', 'swift')}
}
return dictionary
}()`.trim()
default:
return cppParameterName
}
}
case 'function': {
const funcType = getTypeAs(this.type, FunctionType)
switch (language) {
Expand Down Expand Up @@ -271,7 +307,7 @@ export class SwiftCxxBridgedType {
const optional = getTypeAs(this.type, OptionalType)
const wrapping = new SwiftCxxBridgedType(optional.wrappingType)
const bridge = this.getBridgeOrThrow()
const fullName = NitroConfig.getCxxNamespace(language, bridge.funcName)
const fullName = `bridge.${bridge.funcName}`
switch (language) {
case 'swift':
return `
Expand All @@ -297,7 +333,7 @@ export class SwiftCxxBridgedType {
}
case 'array': {
const bridge = this.getBridgeOrThrow()
const fullName = NitroConfig.getCxxNamespace('swift', bridge.funcName)
const fullName = `bridge.${bridge.funcName}`
const array = getTypeAs(this.type, ArrayType)
const wrapping = new SwiftCxxBridgedType(array.itemType)
switch (language) {
Expand All @@ -309,6 +345,26 @@ export class SwiftCxxBridgedType {
vector.push_back(${wrapping.parseFromSwiftToCpp('item', language)})
}
return vector
}()`.trim()
default:
return swiftParameterName
}
}
case 'record': {
const bridge = this.getBridgeOrThrow()
const createMap = `bridge.${bridge.funcName}`
const record = getTypeAs(this.type, RecordType)
const wrappingKey = new SwiftCxxBridgedType(record.keyType)
const wrappingValue = new SwiftCxxBridgedType(record.valueType)
switch (language) {
case 'swift':
return `
{
var map = ${createMap}(${swiftParameterName}.count)
for (k, v) in ${swiftParameterName} {
map[${wrappingKey.parseFromSwiftToCpp('k', 'swift')}] = ${wrappingValue.parseFromSwiftToCpp('v', 'swift')}
}
return map
}()`.trim()
default:
return swiftParameterName
Expand Down
9 changes: 9 additions & 0 deletions packages/nitrogen/src/syntax/swift/SwiftCxxTypeHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ inline ${actualType} create_${name}(size_t size) {
*/
function createCxxUnorderedMapSwiftHelper(type: RecordType): SwiftCxxHelper {
const actualType = type.getCode('c++')
const keyType = type.keyType.getCode('c++')
const name = escapeCppName(type.getCode('c++'))
return {
funcName: `create_${name}`,
Expand All @@ -106,6 +107,14 @@ inline ${actualType} create_${name}(size_t size) {
${actualType} map;
map.reserve(size);
return map;
}
inline std::vector<${keyType}> get_${name}_keys(const ${name}& map) {
std::vector<${keyType}> keys;
keys.reserve(map.size());
for (const auto& entry : map) {
keys.push_back(entry.first);
}
return keys;
}
`.trim(),
}
Expand Down
24 changes: 22 additions & 2 deletions packages/nitrogen/src/syntax/swift/SwiftHybridObjectBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function createSwiftHybridObjectCxxBridge(
spec: HybridObjectSpec
): SourceFile[] {
const name = getHybridObjectName(spec.name)
const moduleName = NitroConfig.getIosModuleName()

const propertiesBridge = spec.properties
.map((p) => getPropertyForwardImplementation(p))
Expand All @@ -46,13 +47,29 @@ import NitroModules
* - Throwing methods need to be wrapped with a Result<T, Error> type, as exceptions cannot be propagated to C++
*/
public final class ${name.HybridTSpecCxx} {
/**
* The Swift <> C++ bridge's namespace (\`${NitroConfig.getCxxNamespace('c++', 'bridge', 'swift')}\`)
* from \`${moduleName}-Swift-Cxx-Bridge.hpp\`.
* This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift.
*/
public typealias bridge = ${NitroConfig.getCxxNamespace('swift', 'bridge', 'swift')}

/**
* Holds an instance of the \`${name.HybridTSpec}\` Swift protocol.
*/
private(set) var implementation: ${name.HybridTSpec}

/**
* Create a new \`${name.HybridTSpecCxx}\` that wraps the given \`${name.HybridTSpec}\`.
* All properties and methods bridge to C++ types.
*/
public init(_ implementation: ${name.HybridTSpec}) {
self.implementation = implementation
}

// HybridObject C++ part
/**
* Contains a (weak) reference to the C++ HybridObject to cache it.
*/
public var hybridContext: margelo.nitro.HybridContext {
get {
return self.implementation.hybridContext
Expand All @@ -62,7 +79,10 @@ public final class ${name.HybridTSpecCxx} {
}
}

// Memory size of the Swift class (plus size of any other allocations)
/**
* Get the memory size of the Swift class (plus size of any other allocations)
* so the JS VM can properly track it and garbage-collect the JS object if needed.
*/
public var memorySize: Int {
return self.implementation.memorySize
}
Expand Down
2 changes: 2 additions & 0 deletions packages/nitrogen/src/syntax/types/RecordType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export class RecordType implements Type {
switch (language) {
case 'c++':
return `std::unordered_map<${keyCode}, ${valueCode}>`
case 'swift':
return `Dictionary<${keyCode}, ${valueCode}>`
default:
throw new Error(
`Language ${language} is not yet supported for RecordType!`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,29 @@ class HybridSwiftKotlinTestObject : HybridSwiftKotlinTestObjectSpec {
callback()
}
}

var someMap: Dictionary<String, Double> {
get {
return [
"hello": 55.0
]
}
set {

}
}

var someArray: [String] {
get {
return ["hello"]
}
set {

}
}

var someOptional: String? {
get { return "Hello" }
set { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ namespace margelo::nitro::image { struct Person; }
#include "Person.hpp"
#include <functional>
#include <optional>
#include <unordered_map>
#include <vector>

namespace margelo::nitro::image {
/**
* Contains specialized versions of C++ templated types so they can be accessed from Swift,
* as well as helper functions to interact with those C++ types from Swift.
*/
namespace margelo::nitro::image::bridge::swift {

using Func_void_std__string = std::function<void(const std::string&)>;

Expand Down Expand Up @@ -56,5 +61,20 @@ namespace margelo::nitro::image {
using Func_std__future_double_ = std::function<std::future<double>()>;

using Func_std__future_std__string_ = std::function<std::future<std::string>()>;

using std__unordered_map_std__string__double_ = std::unordered_map<std::string, double>;
inline std::unordered_map<std::string, double> create_std__unordered_map_std__string__double_(size_t size) {
std::unordered_map<std::string, double> map;
map.reserve(size);
return map;
}
inline std::vector<std::string> get_std__unordered_map_std__string__double__keys(const std__unordered_map_std__string__double_& map) {
std::vector<std::string> keys;
keys.reserve(map.size());
for (const auto& entry : map) {
keys.push_back(entry.first);
}
return keys;
}

} // namespace margelo::nitro::image
} // namespace margelo::nitro::image::bridge::swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ namespace margelo::nitro::image {
inline void setOptionalString(const std::optional<std::string>& optionalString) noexcept override {
_swiftPart.setOptionalString(optionalString);
}
inline std::unordered_map<std::string, double> getSomeMap() noexcept override {
auto result = _swiftPart.getSomeMap();
return result;
}
inline void setSomeMap(const std::unordered_map<std::string, double>& someMap) noexcept override {
_swiftPart.setSomeMap(someMap);
}
inline std::vector<std::string> getSomeArray() noexcept override {
auto result = _swiftPart.getSomeArray();
return result;
}
inline void setSomeArray(const std::vector<std::string>& someArray) noexcept override {
_swiftPart.setSomeArray(someArray);
}
inline std::optional<std::string> getSomeOptional() noexcept override {
auto result = _swiftPart.getSomeOptional();
return result;
}
inline void setSomeOptional(const std::optional<std::string>& someOptional) noexcept override {
_swiftPart.setSomeOptional(someOptional);
}

public:
// Methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,29 @@ import NitroModules
* - Throwing methods need to be wrapped with a Result<T, Error> type, as exceptions cannot be propagated to C++
*/
public final class HybridImageFactorySpecCxx {
/**
* The Swift <> C++ bridge's namespace (`margelo::nitro::image::bridge::swift`)
* from `NitroImage-Swift-Cxx-Bridge.hpp`.
* This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift.
*/
public typealias bridge = margelo.nitro.image.bridge.swift

/**
* Holds an instance of the `HybridImageFactorySpec` Swift protocol.
*/
private(set) var implementation: HybridImageFactorySpec

/**
* Create a new `HybridImageFactorySpecCxx` that wraps the given `HybridImageFactorySpec`.
* All properties and methods bridge to C++ types.
*/
public init(_ implementation: HybridImageFactorySpec) {
self.implementation = implementation
}

// HybridObject C++ part
/**
* Contains a (weak) reference to the C++ HybridObject to cache it.
*/
public var hybridContext: margelo.nitro.HybridContext {
get {
return self.implementation.hybridContext
Expand All @@ -35,7 +51,10 @@ public final class HybridImageFactorySpecCxx {
}
}

// Memory size of the Swift class (plus size of any other allocations)
/**
* Get the memory size of the Swift class (plus size of any other allocations)
* so the JS VM can properly track it and garbage-collect the JS object if needed.
*/
public var memorySize: Int {
return self.implementation.memorySize
}
Expand Down
Loading