diff --git a/Sources/DistributedActors/ClusterSystem.swift b/Sources/DistributedActors/ClusterSystem.swift index 98714de27..3982070d4 100644 --- a/Sources/DistributedActors/ClusterSystem.swift +++ b/Sources/DistributedActors/ClusterSystem.swift @@ -1052,6 +1052,7 @@ extension ClusterSystem { let invocation = InvocationMessage( callID: callID, targetIdentifier: target.identifier, + genericSubstitutions: invocation.genericSubstitutions, arguments: arguments ) recipient.sendInvocation(invocation) @@ -1094,6 +1095,7 @@ extension ClusterSystem { let invocation = InvocationMessage( callID: callID, targetIdentifier: target.identifier, + genericSubstitutions: invocation.genericSubstitutions, arguments: arguments ) recipient.sendInvocation(invocation) diff --git a/Sources/DistributedActors/InvocationBehavior.swift b/Sources/DistributedActors/InvocationBehavior.swift index f51cbcf7c..f641f65d2 100644 --- a/Sources/DistributedActors/InvocationBehavior.swift +++ b/Sources/DistributedActors/InvocationBehavior.swift @@ -20,6 +20,7 @@ import struct Foundation.Data public struct InvocationMessage: Sendable, Codable, CustomStringConvertible { let callID: ClusterSystem.CallID let targetIdentifier: String + let genericSubstitutions: [String] let arguments: [Data] var target: RemoteCallTarget { @@ -27,7 +28,7 @@ public struct InvocationMessage: Sendable, Codable, CustomStringConvertible { } public var description: String { - "InvocationMessage(callID: \(callID), target: \(target), arguments: \(arguments.count))" + "InvocationMessage(callID: \(callID), target: \(target), genericSubstitutions: \(genericSubstitutions), arguments: \(arguments.count))" } } diff --git a/Sources/DistributedActors/Serialization/Serialization+Invocation.swift b/Sources/DistributedActors/Serialization/Serialization+Invocation.swift index b8a0fc8a1..9de662349 100644 --- a/Sources/DistributedActors/Serialization/Serialization+Invocation.swift +++ b/Sources/DistributedActors/Serialization/Serialization+Invocation.swift @@ -21,6 +21,7 @@ import SwiftProtobuf public struct ClusterInvocationEncoder: DistributedTargetInvocationEncoder { public typealias SerializationRequirement = any Codable + var genericSubstitutions: [String] = [] var arguments: [Data] = [] var throwing: Bool = false @@ -31,7 +32,8 @@ public struct ClusterInvocationEncoder: DistributedTargetInvocationEncoder { } public mutating func recordGenericSubstitution(_ type: T.Type) throws { - fatalError("NOT IMPLEMENTED: \(#function)") + let typeName = _mangledTypeName(type) ?? _typeName(type) + self.genericSubstitutions.append(typeName) } public mutating func recordArgument(_ argument: RemoteCallArgument) throws { @@ -72,7 +74,20 @@ public struct ClusterInvocationDecoder: DistributedTargetInvocationDecoder { } public mutating func decodeGenericSubstitutions() throws -> [Any.Type] { - fatalError("NOT IMPLEMENTED: \(#function)") + let genericSubstitutions: [String] + switch self.state { + case .remoteCall(_, let message): + genericSubstitutions = message.genericSubstitutions + case .localProxyCall(let invocation): + genericSubstitutions = invocation.genericSubstitutions + } + + return try genericSubstitutions.map { + guard let type = _typeByName($0) else { + throw SerializationError.notAbleToDeserialize(hint: $0) + } + return type + } } public mutating func decodeNextArgument() throws -> Argument { diff --git a/Tests/DistributedActorsTests/ClusterSystemTests.swift b/Tests/DistributedActorsTests/ClusterSystemTests.swift index 30d2b7608..c6e17b5ad 100644 --- a/Tests/DistributedActorsTests/ClusterSystemTests.swift +++ b/Tests/DistributedActorsTests/ClusterSystemTests.swift @@ -378,6 +378,25 @@ final class ClusterSystemTests: ClusterSystemXCTestCase { throw testKit.fail("Expected timeout to be 200 milliseconds but was \(timeoutError.timeout)") } } + + func test_remoteCallGeneric() async throws { + let local = await setUpNode("local") { settings in + settings.enabled = true + } + let remote = await setUpNode("remote") { settings in + settings.enabled = true + } + local.cluster.join(node: remote.cluster.uniqueNode) + + let greeter = Greeter(actorSystem: local) + let remoteGreeterRef = try Greeter.resolve(id: greeter.id, using: remote) + + let message: String = "hello" + let value = try await shouldNotThrow { + try await remoteGreeterRef.echo(message) + } + value.shouldEqual(message) + } } private distributed actor Greeter { @@ -414,6 +433,10 @@ private distributed actor Greeter { try await Task.sleep(nanoseconds: delayNanos) try await self.muted() } + + distributed func echo(_ message: T) -> T { + message + } } private struct GreeterCodableError: Error, Codable {}