Skip to content
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
16 changes: 12 additions & 4 deletions Sources/ComposeUI/KotlinBindings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,24 @@ import SwiftJava
open class ViewNodeObject: JavaObject {

/// The bridge constructor: one JNI call per node, arrays crossing as
/// single arguments. Prop/modifier-arg values are JSON literals; negative
/// count/provider mean "absent".
/// single arguments. Scalars cross typed — a kind tag per value selects
/// the string slot or the bits slot (doubles bit-cast into the long) —
/// so only array-valued props take the JSON path. Negative count/provider
/// mean "absent".
@JavaMethod
@_nonoverride public convenience init(
_ type: String,
_ id: String,
_ propKeys: [String],
_ propValues: [String],
_ propKinds: [Int32],
_ propStrings: [String],
_ propBits: [Int64],
_ modifierKinds: [String],
_ modifierArgs: [String],
_ modifierArgCounts: [Int32],
_ argKeys: [String],
_ argKinds: [Int32],
_ argStrings: [String],
_ argBits: [Int64],
_ children: [ViewNodeObject?],
_ count: Int32,
_ itemProviderId: Int64,
Expand Down
59 changes: 53 additions & 6 deletions Sources/ComposeUI/Materializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,75 @@ import SwiftJava

public enum Materializer {

// value kinds understood by the Kotlin bridge constructor
private static let kindString: Int32 = 0
private static let kindDouble: Int32 = 1
private static let kindBool: Int32 = 2
private static let kindInt: Int32 = 3
private static let kindJSON: Int32 = 4

/// One scalar's typed slots: strings ride the string slot; numbers and
/// bools ride the bits slot (doubles bit-cast, so 64-bit callback ids and
/// doubles both cross exactly); only arrays fall back to JSON text.
private static func slots(for value: PropValue) -> (kind: Int32, string: String, bits: Int64) {
switch value {
case .string(let string):
return (kindString, string, 0)
case .double(let double):
return (kindDouble, "", Int64(bitPattern: double.bitPattern))
case .bool(let bool):
return (kindBool, "", bool ? 1 : 0)
case .int(let int):
return (kindInt, "", Int64(int))
case .array:
return (kindJSON, jsonLiteral(value), 0)
}
}

/// Builds the Kotlin mirror of an IR tree, depth-first.
public static func materialize(_ node: RenderNode) -> ViewNodeObject {
var propKeys: [String] = []
var propValues: [String] = []
var propKinds: [Int32] = []
var propStrings: [String] = []
var propBits: [Int64] = []
for (key, value) in node.props {
let slot = slots(for: value)
propKeys.append(key)
propValues.append(jsonLiteral(value))
propKinds.append(slot.kind)
propStrings.append(slot.string)
propBits.append(slot.bits)
}
var modifierKinds: [String] = []
var modifierArgs: [String] = []
var modifierArgCounts: [Int32] = []
var argKeys: [String] = []
var argKinds: [Int32] = []
var argStrings: [String] = []
var argBits: [Int64] = []
for modifier in node.modifiers {
modifierKinds.append(modifier.kind)
modifierArgs.append(jsonObjectLiteral(modifier.args))
modifierArgCounts.append(Int32(modifier.args.count))
for (key, value) in modifier.args {
let slot = slots(for: value)
argKeys.append(key)
argKinds.append(slot.kind)
argStrings.append(slot.string)
argBits.append(slot.bits)
}
}
let children = node.children.map { materialize($0) as ViewNodeObject? }
return ViewNodeObject(
node.type,
node.id,
propKeys,
propValues,
propKinds,
propStrings,
propBits,
modifierKinds,
modifierArgs,
modifierArgCounts,
argKeys,
argKinds,
argStrings,
argBits,
children,
Int32(node.count ?? -1),
Int64(-1)
Expand Down
53 changes: 45 additions & 8 deletions composeui/src/commonMain/kotlin/com/pureswift/swiftui/ViewNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,68 @@ data class ViewNode(

/// Bridge constructor: the Swift materializer builds nodes through this,
/// crossing JNI with flat arrays (one call per node, arrays as single
/// arguments). Prop and modifier-arg values arrive as JSON literals
/// ("\"text\"", "42", "true"), keeping the typed JsonObject model without
/// per-field JNI calls. Negative count/provider mean "absent".
/// arguments). Scalar values cross typed — a kind tag selects between the
/// string slot and the bits slot (doubles bit-cast into the long, so both
/// 64-bit callback ids and doubles cross exactly) — so no value takes the
/// JSON parser except array-valued props. Modifier args flatten into one
/// run of slots, `modifierArgCounts` giving each modifier's share.
/// Negative count/provider mean "absent".
constructor(
type: String,
id: String,
propKeys: Array<String>,
propValues: Array<String>,
propKinds: IntArray,
propStrings: Array<String>,
propBits: LongArray,
modifierKinds: Array<String>,
modifierArgs: Array<String>,
modifierArgCounts: IntArray,
argKeys: Array<String>,
argKinds: IntArray,
argStrings: Array<String>,
argBits: LongArray,
children: Array<ViewNode>,
count: Int,
itemProviderId: Long,
) : this(
type = type,
id = id,
props = JsonObject(propKeys.indices.associate { propKeys[it] to Json.parseToJsonElement(propValues[it]) }),
modifiers = modifierKinds.indices.map {
ModifierNode(modifierKinds[it], Json.parseToJsonElement(modifierArgs[it]).jsonObject)
props = JsonObject(propKeys.indices.associate {
propKeys[it] to jsonValue(propKinds[it], propStrings[it], propBits[it])
}),
modifiers = buildList {
var base = 0
for (m in modifierKinds.indices) {
val argCount = modifierArgCounts[m]
add(ModifierNode(modifierKinds[m], JsonObject((0 until argCount).associate {
val i = base + it
argKeys[i] to jsonValue(argKinds[i], argStrings[i], argBits[i])
})))
base += argCount
}
},
children = children.toList(),
count = if (count >= 0) count else null,
itemProviderId = if (itemProviderId >= 0) itemProviderId else null,
)

companion object {
// value kinds used by the bridge constructor
private const val KIND_STRING = 0
private const val KIND_DOUBLE = 1
private const val KIND_BOOL = 2
private const val KIND_INT = 3
private const val KIND_JSON = 4

private fun jsonValue(kind: Int, string: String, bits: Long): kotlinx.serialization.json.JsonElement =
when (kind) {
KIND_STRING -> JsonPrimitive(string)
KIND_DOUBLE -> JsonPrimitive(Double.fromBits(bits))
KIND_BOOL -> JsonPrimitive(bits != 0L)
KIND_INT -> JsonPrimitive(bits)
else -> Json.parseToJsonElement(string) // arrays only
}
}

// Typed prop accessors used by the interpreter.

fun string(key: String): String? = (props[key] as? JsonPrimitive)?.content
Expand Down
Loading