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

Hardcoded constructors marshaling fix, some leaks fixes #550

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
114 changes: 67 additions & 47 deletions Generator/Generator/BuiltinGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -607,33 +607,44 @@ func generateBuiltinClasses (values: [JGodotBuiltinClass], outputDir: String?) a

p ("public \(kind == .isStruct ? "struct" : "class") \(typeName)\(proto)") {
if bc.name == "String" {
p ("public required init (_ str: String)") {
p ("gi.string_new_with_utf8_chars (&content, str)")
p("""
public required init(_ string: String) {
gi.string_new_with_utf8_chars(&content, string)
}
p ("// ExpressibleByStringLiteral conformace")
p ("public required init (stringLiteral value: String)") {
p ("gi.string_new_with_utf8_chars (&content, value)")
""")

p("""
// ExpressibleByStringLiteral conformance
public required init(stringLiteral value: String) {
gi.string_new_with_utf8_chars(&content, value)
}
""")
}
if bc.name == "NodePath" {
p ("// ExpressibleByStringLiteral conformace")
p ("public required init (stringLiteral value: String)") {
p ("let from = GString (value)")
p ("var args: [UnsafeRawPointer?] = []")
p ("withUnsafePointer (to: &from.content)", arg: " ptr in") {
p ("args.append (ptr)")
p ("NodePath.constructor2 (&content, &args)")
p("""
/// ExpressibleByStringLiteral conformance
public required init(stringLiteral value: String) {
let gstring = GString(value)
withUnsafePointer(to: &gstring.content) { pContent in
withUnsafePointer(to: pContent) { pArgs in
NodePath.constructor2(&content, pArgs)
}
}
}
p ("// LosslessStringConvertible conformance)")
p ("public required init (_ value: String)") {
p ("let from = GString (value)")
p ("var args: [UnsafeRawPointer?] = []")
p ("withUnsafePointer (to: &from.content)", arg: " ptr in") {
p ("args.append (ptr)")
p ("NodePath.constructor2 (&content, &args)")
""")

p("""
/// LosslessStringConvertible conformance
public required init(_ value: String) {
let gstring = GString(value)
withUnsafePointer(to: &gstring.content) { pContent in
withUnsafePointer(to: pContent) { pArgs in
NodePath.constructor2(&content, pArgs)
}
}
}
""")

p ("/// Produces a string representation of this NodePath")
p ("public var description: String") {
p ("let sub = getSubnameCount () > 0 ? getConcatenatedSubnames ().description : \"\"")
Expand All @@ -646,30 +657,37 @@ func generateBuiltinClasses (values: [JGodotBuiltinClass], outputDir: String?) a
// really produce this when it matches the kind
// directly to be the one that takes a StringName
// parameter
p ("public init (fromPtr: UnsafeRawPointer?)") {
p ("var args: [UnsafeRawPointer?] = [")
p (" fromPtr,")
p ("]")
p ("StringName.constructor1 (&content, &args)")
}
p ("// ExpressibleByStringLiteral conformace")
p ("public required init (stringLiteral value: String)") {
p ("let from = GString (value)")
p ("var args: [UnsafeRawPointer?] = []")
p ("withUnsafePointer (to: &from.content)", arg: " ptr in"){
p ("args.append (ptr)")
p ("StringName.constructor2 (&content, &args)")
p("""
public init(fromPtr ptr: UnsafeRawPointer?) {
withUnsafePointer(to: ptr) { pArgs in
StringName.constructor1(&content, pArgs)
}
}
p ("// LosslessStringConvertible conformance)")
p ("public required init (_ value: String)") {
p ("let from = GString (value)")
p ("var args: [UnsafeRawPointer?] = []")
p ("withUnsafePointer (to: &from.content)", arg: " ptr in"){
p ("args.append (ptr)")
p ("StringName.constructor2 (&content, &args)")
""")

p("""
/// ExpressibleByStringLiteral conformace
public required init(stringLiteral value: String) {
let gstring = GString(value)
withUnsafePointer(to: &gstring.content) { pContent in
withUnsafePointer(to: pContent) { pArgs in
StringName.constructor2(&content, pArgs)
}
}
}
""")

p("""
/// LosslessStringConvertible conformance
public required init(_ value: String) {
let gstring = GString(value)
withUnsafePointer(to: &gstring.content) { pContent in
withUnsafePointer(to: pContent) { pArgs in
StringName.constructor2(&content, pArgs)
}
}
}
""")
}
if bc.name == "Callable" {
p ("/// Creates a Callable instance from a Swift function")
Expand Down Expand Up @@ -715,16 +733,18 @@ func generateBuiltinClasses (values: [JGodotBuiltinClass], outputDir: String?) a
// hardcoding the constructor1 here, it should
// really produce this when it matches the kind
// directly to be the one that takes the same
// parameter
p ("// Used to construct objects on virtual proxies")
p ("public required init (content: ContentType)") {
p ("var copy = content")
p ("var args: [UnsafeRawPointer?] = []")
p ("withUnsafePointer (to: &copy)", arg: " ptr in") {
p ("args.append (ptr)")
p ("\(typeName).constructor1 (&self.content, &args)")
// parameter
p("""
// Used to construct objects on virtual proxies
public required init(content proxyContent: ContentType) {
withUnsafePointer(to: proxyContent) { pContent in
withUnsafePointer(to: pContent) { pArgs in
\(typeName).constructor1(&content, pArgs)
}
}
}
""")

p ("// Used to construct objects when the underlying built-in's ref count has already been incremented for me")
p ("public required init(alreadyOwnedContent content: ContentType)") {
p ("self.content = content")
Expand Down
26 changes: 19 additions & 7 deletions Generator/Generator/MethodGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,11 @@ func methodGenNew(_ p: Printer, method: MethodDefinition, className: String, cde
fatalError ("If the returnType is not empty, we should have a godotReturnType")
}
if method.isVararg {
return "var _result: Variant.ContentType = Variant.zero"
if godotReturnType == "String" {
return "let _result = GString()"
} else {
return "var _result: Variant.ContentType = Variant.zero"
}
} else if godotReturnType.starts(with: "typedarray::") {
let (storage, initialize) = getBuiltinStorage ("Array")
return "var _result: \(storage)\(initialize)"
Expand Down Expand Up @@ -270,7 +274,11 @@ func methodGenNew(_ p: Printer, method: MethodDefinition, className: String, cde
guard let godotReturnType else { fatalError("godotReturnType is nil!") }

if method.isVararg {
ptrResult = "&_result"
if godotReturnType == "String" {
ptrResult = "&_result.content"
} else {
ptrResult = "&_result"
}
} else if argTypeNeedsCopy(godotType: godotReturnType) {
let isClass = builtinGodotTypeNames [godotReturnType] == .isClass

Expand Down Expand Up @@ -379,11 +387,11 @@ func methodGenNew(_ p: Printer, method: MethodDefinition, className: String, cde
guard returnType != "" else { return "" }
if method.isVararg {
if returnType == "Variant" {
return "return Variant(copying: _result)"
return "return Variant(takingOver: _result)"
} else if returnType == "GodotError" {
return "return GodotError(rawValue: Int64(Variant(copying: _result))!)!"
} else if returnType == "String" {
return "return GString(Variant(copying: _result))?.description ?? \"\""
return "return _result.description"
} else {
fatalError("Do not support this return type = \(returnType)")
}
Expand Down Expand Up @@ -767,7 +775,11 @@ func methodGenLegacy(_ p: Printer, method: MethodDefinition, className: String,
guard let godotReturnType else { fatalError("godotReturnType is nil!") }

if method.isVararg {
ptrResult = "&_result"
if godotReturnType == "String" {
ptrResult = "&_result.content"
} else {
ptrResult = "&_result"
}
} else if argTypeNeedsCopy(godotType: godotReturnType) {
let isClass = builtinGodotTypeNames [godotReturnType] == .isClass

Expand Down Expand Up @@ -848,11 +860,11 @@ func methodGenLegacy(_ p: Printer, method: MethodDefinition, className: String,
guard returnType != "" else { return "" }
if method.isVararg {
if returnType == "Variant" {
return "return Variant(copying: _result)"
return "return Variant(takingOver: _result)"
} else if returnType == "GodotError" {
return "return GodotError(rawValue: Int64(Variant(copying: _result))!)!"
} else if returnType == "String" {
return "return GString(Variant(copying: _result))?.description ?? \"\""
return "return _result.description"
} else {
fatalError("Do not support this return type = \(returnType)")
}
Expand Down
89 changes: 89 additions & 0 deletions Tests/SwiftGodotTests/MemoryLeakTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,95 @@ final class MemoryLeakTests: GodotTestCase {
XCTAssertEqual(variant[0], Variant("U"))
}

func test_emit_signal_leak() {
let object = Object()
let signal = SignalWithNoArguments("some_random_name")

checkLeaks {
for _ in 0 ..< 200 {
_ = object.emit(signal: signal)
}
}
}

func test_gstring_string_variant_leak() {
let gstrFoo = GString("Foo")

checkLeaks {
for _ in 0 ..< 200 {
let strFoo = String(gstrFoo)
let varFoo = Variant(gstrFoo)
let strFoo0 = varFoo.description
guard let gstrFoo0 = GString(varFoo) else {
XCTFail()

return
}
let strFoo1 = String(gstrFoo0)
}
}

checkLeaks {
for _ in 0 ..< 200 {
let gstrBar = GString("Bar")

let strBar = String(gstrBar)
let varBar = Variant(gstrBar)
let strBar0 = varBar.description
guard let gstrBar0 = GString(varBar) else {
XCTFail()

return
}
let strBar1 = String(gstrBar0)
}
}
}

// https://github.com/migueldeicaza/SwiftGodot/issues/551
func test_551_leak() {
checkLeaks {
for i in 0 ..< 200 {
let str = GD.str(arg1: Variant(i))
XCTAssertEqual("\(i)", str)
}
}
}

// https://github.com/migueldeicaza/SwiftGodot/issues/552
func test_552_leak() {
checkLeaks {
for _ in 0 ..< 200 {
let object = Object()
let methodName = StringName("get_method_list")
let methodList = object.call(method: methodName)
}
}
}

func test_godot_string_description_leak() {
checkLeaks {
let string = GString("A")
for _ in 0 ..< 100 {
print(string.description)
}
}
}

func test_godot_string_from_variant_leak() {
let variant = Variant("A")
checkLeaks {
for _ in 0 ..< 100 {
guard let gstring = GString(variant) else {
XCTFail()
break
}

print(gstring.description)
}
}
}

func test_531_crash_or_leak() {
checkLeaks {
let g = GodotEncoder()
Expand Down