| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-03-04 14:59:22 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-03-04 19:28:26 UTC |
| parent | ea88903bd04a124c8e60095f9b6d0dd4cbe52235 |
| HaxeSwiftBridge.hx | +43 | -4 |
| borogove/Client.hx | +15 | -0 |
| borogove/EventEmitter.hx | +3 | -1 |
diff --git a/HaxeSwiftBridge.hx b/HaxeSwiftBridge.hx index e09750a..46d5763 100644 --- a/HaxeSwiftBridge.hx +++ b/HaxeSwiftBridge.hx @@ -272,6 +272,15 @@ class HaxeSwiftBridge { } } + static function identToStr(expr: Expr) { + switch (expr.expr) { + case EConst(CIdent(s)): + return s; + default: + throw "ident expencted"; + } + } + static function metaIsSwiftExpose(e: ExprDef) { return switch (e) { case ECall(call, args): @@ -424,7 +433,6 @@ class HaxeSwiftBridge { } if (fargs.length > 0) ibuilder.add(", "); ibuilder.add("ctx"); - // TODO unretained vs retained ibuilder.add(") in\n\t\t\t\tlet "); ibuilder.add(arg.name); ibuilder.add(" = Unmanaged<AnyObject>.fromOpaque(ctx!).takeUnretainedValue() as! "); @@ -463,8 +471,27 @@ class HaxeSwiftBridge { default: } ibuilder.add("\n\t\t)"); - builder.add("return "); + builder.add("let __result = "); builder.add(castToSwift(ibuilder.toString(), finalTret, false, true)); + for (arg in targs) { + switch TypeTools.followWithAbstracts(Context.resolveType(Context.toComplexType(arg.t), Context.currentPos()), false) { + case TFun(fargs, fret): + final contextLifetime = fld.meta.filter(meta -> meta.name == ":HaxeSwiftBridge.contextLifetime").map(meta -> meta.params.map(identToStr)).find(params -> params[0] == arg.name); + if (contextLifetime != null) { + builder.add("\n\t\t" + contextLifetime[1] + ".__contextLifetime[__result] = __" + arg.name + "_ptr"); + } + builder.add("\n\t\tcontextLifetime[o] = (contextLifetime[o] ?? []) + [__" + arg.name + "_ptr]"); + default: + } + } + final lifetimeEnds = fld.meta.find(meta -> meta.name == ":HaxeSwiftBridge.contextLifetimeEnds"); + if (lifetimeEnds != null) { + builder.add("\n\t\tSelf.__contextLifetime[" + identToStr(lifetimeEnds.params[0]) + "].map { ending in\n"); + builder.add("\t\t\tUnmanaged<AnyObject>.fromOpaque(ending).release()\n"); + builder.add("\t\t\tcontextLifetime[o] = contextLifetime[o]?.filter { $0 != ending }\n"); + builder.add("\t\t}"); + } + builder.add("\n\t\treturn __result"); for (arg in targs) { switch TypeTools.followWithAbstracts(Context.resolveType(Context.toComplexType(arg.t), Context.currentPos()), false) { case TInst(_.get().name => "Array", [TInst(_.get().name => "String", _)]): @@ -529,8 +556,11 @@ class HaxeSwiftBridge { builder.add(" {\n"); if (!cls.isInterface && superClass == null) { // We don't want this to be public, but it needs to be for the protocol, hmm - builder.add("\tpublic let o: UnsafeMutableRawPointer\n\n"); - builder.add("\tinternal init(_ ptr: UnsafeMutableRawPointer) {\n\t\to = ptr\n\t}\n\n"); + builder.add("\tpublic let o: UnsafeMutableRawPointer\n"); + if (!cls.meta.extract(":HaxeSwiftBridge.contextLifetime").empty()) { + builder.add("\tinternal static var __contextLifetime: [Int32: UnsafeMutableRawPointer] = [:]\n"); + } + builder.add("\n\tinternal init(_ ptr: UnsafeMutableRawPointer) {\n\t\to = ptr\n\t\tc_borogove.borogove_set_finalizer(ptr, releaseContexts)\n\t}\n\n"); } if (!cls.isInterface && superClass != null) { @@ -806,6 +836,15 @@ class HaxeSwiftBridge { return useString(UnsafePointer(mptr?.assumingMemoryBound(to: CChar.self))) } + internal var contextLifetime: [UnsafeMutableRawPointer: [UnsafeMutableRawPointer]] = [:] + + internal func releaseContexts(o: UnsafeMutableRawPointer?) { + if let o { + contextLifetime[o]?.map { Unmanaged<AnyObject>.fromOpaque($0).release() } + contextLifetime[o] = nil + } + } + // From https://github.com/swiftlang/swift/blob/dfc3933a05264c0c19f7cd43ea0dca351f53ed48/stdlib/private/SwiftPrivate/SwiftPrivate.swift public func scan< S : Sequence, U diff --git a/borogove/Client.hx b/borogove/Client.hx index a19a29c..582a50f 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -1219,6 +1219,7 @@ class Client extends EventEmitter { @param handler takes one argument, the Client that needs a password @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addPasswordNeededListener(handler:Client->Void) { return this.on("auth/password-needed", (data) -> { handler(this); @@ -1232,6 +1233,7 @@ class Client extends EventEmitter { @param handler takes no arguments @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addStatusOnlineListener(handler:()->Void) { return this.on("status/online", (data) -> { handler(); @@ -1245,6 +1247,7 @@ class Client extends EventEmitter { @param handler takes no arguments @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addStatusOfflineListener(handler:()->Void) { return this.on("status/offline", (data) -> { handler(); @@ -1258,6 +1261,7 @@ class Client extends EventEmitter { @param handler takes no arguments @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addConnectionFailedListener(handler:()->Void) { return stream.on("status/error", (data) -> { handler(); @@ -1271,6 +1275,7 @@ class Client extends EventEmitter { @param handler takes two arguments, the PEM of the cert and an array of DNS names, and must return true to accept or false to reject @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addTlsCheckListener(handler:(String, Array<String>)->Bool) { return stream.on("tls/check", (data) -> { return EventValue(handler(data.pem, data.dnsNames)); @@ -1281,6 +1286,7 @@ class Client extends EventEmitter { // TODO: haxe cpp erases enum into int, so using it as a callback arg is hard // could just use int in C bindings, or need to come up with a good strategy // for the wrapper + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addUserStateListener(handler: (String,String,Null<String>,UserState)->Void):EventHandlerToken { return this.on("chat-state/update", (data) -> { handler(data.message.senderId, data.message.chatId, data.message.threadId, data.userState); @@ -1299,6 +1305,7 @@ class Client extends EventEmitter { **/ #if cpp // HaxeCBridge doesn't support "secondary" enums yet + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addChatMessageListener(handler:(ChatMessage, Int)->Void) { #else public function addChatMessageListener(handler:(ChatMessage, ChatMessageEvent)->Void):EventHandlerToken { @@ -1316,6 +1323,7 @@ class Client extends EventEmitter { @param handler takes one argument, the ChatMessage @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addSyncMessageListener(handler:(ChatMessage)->Void):EventHandlerToken { return this.on("message/sync", (data) -> { handler(data); @@ -1329,6 +1337,7 @@ class Client extends EventEmitter { @param handler takes one argument, an array of Chats that were updated @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addChatsUpdatedListener(handler:Array<Chat>->Void) { final updateChatBuffer: Map<String, Chat> = []; var lastCall = -1.0; @@ -1365,6 +1374,7 @@ class Client extends EventEmitter { @param handler takes one argument, the call Session @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addCallRingListener(handler:(Session)->Void) { return this.on("call/ring", (data) -> { handler(data.session); @@ -1378,6 +1388,7 @@ class Client extends EventEmitter { @param handler takes two arguments, the associated Chat ID and Session ID @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addCallRetractListener(handler:(String,String)->Void) { return this.on("call/retract", (data) -> { handler(data.chatId, data.sid); @@ -1391,6 +1402,7 @@ class Client extends EventEmitter { @param handler takes one argument, the associated Session @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addCallRingingListener(handler:(Session)->Void) { return this.on("call/ringing", (data) -> { handler(data); @@ -1404,6 +1416,7 @@ class Client extends EventEmitter { @param handler takes one argument, the associated Session @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addCallUpdateStatusListener(handler:(InitiatedSession)->Void) { return this.on("call/updateStatus", (data) -> { handler(data.session); @@ -1419,6 +1432,7 @@ class Client extends EventEmitter { and a boolean indicating if video is desired @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addCallMediaListener(handler:(InitiatedSession,Bool,Bool)->Void) { return this.on("call/media", (data) -> { handler(data.session, data.audio, data.video); @@ -1433,6 +1447,7 @@ class Client extends EventEmitter { the new MediaStreamTrack, and an array of any associated MediaStreams @returns token for use with removeEventListener **/ + @:HaxeSwiftBridge.contextLifetime(handler, EventEmitter) public function addCallTrackListener(handler:(InitiatedSession,MediaStreamTrack,Array<MediaStream>)->Void) { return this.on("call/track", (data) -> { handler(data.session, data.track, data.streams); diff --git a/borogove/EventEmitter.hx b/borogove/EventEmitter.hx index da556a7..271a6ce 100644 --- a/borogove/EventEmitter.hx +++ b/borogove/EventEmitter.hx @@ -20,8 +20,9 @@ typedef EventHandlerToken = Int; @:build(HaxeCBridge.expose()) @:build(HaxeSwiftBridge.expose()) #end +@:HaxeSwiftBridge.contextLifetime class EventEmitter { - private var nextEventHandlerToken = 0; + private static var nextEventHandlerToken = 0; private var eventHandlers:Map<String,Map<EventHandlerToken, EventCallback>> = []; private function new() { } @@ -72,6 +73,7 @@ class EventEmitter { @param token the token that was returned when the listener was added **/ + @:HaxeSwiftBridge.contextLifetimeEnds(token) public function removeEventListener(token:EventHandlerToken) { for (handlers in eventHandlers) { handlers.remove(token);