Skip to content

Commit

Permalink
Finally happy with collections.
Browse files Browse the repository at this point in the history
Clean up the support for collections and other bits.

1. Built-in types now surface a zero property, that can be used to
initialize those instances, this will be needed to not hard-code the
initializer for future support of the larger-data sized versions of
Godot.

2. Make the GodotVariant protocol require an init constructor from a
variant, it forces the user to add a required constructor, but I tried
too many alterantives, and none worked: I need a way for the
GodotCollection to instantiate the subclases of Object.

3. Found a user for the liveFramerowkrTypes: to surface
the-most-derived type in situations like adding some nodes to a
collection, and then retrieving them.

4. Replaced a lot of manual code with generated code.
migueldeicaza committed Apr 11, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 569a9d5 commit 6754671
Showing 6 changed files with 122 additions and 564 deletions.
34 changes: 33 additions & 1 deletion Generator/Generator/BuiltinGen.swift
Original file line number Diff line number Diff line change
@@ -351,7 +351,9 @@ func generateBuiltinClasses (values: [JGodotBuiltinClass], outputDir: String) {
let (storage, initialize) = getBuiltinStorage (bc.name)
p ("// Contains a binary blob where this type information is stored")
p ("var content: ContentType\(initialize)")
p ("")
p ("// Used to initialize empty types")
p ("static let zero: ContentType \(initialize)")
p ("// Convenience type that matches the build configuration storage needs")
p ("typealias ContentType = \(storage)")
builtinClassStorage [bc.name] = storage
// TODO: This is a little brittle, because I am
@@ -366,6 +368,36 @@ func generateBuiltinClasses (values: [JGodotBuiltinClass], outputDir: String) {
p ("StringName.constructor1 (&self.content, &args)")
}
}

p ("/// Creates a new instance from the given variant if it contains a \(typeName)")
let gtype = gtypeFromTypeName (bc.name)
// Now generate the variant constructor
if kind == "class" {
b ("public required init? (_ from: Variant)") {
b ("guard from.gtype == .\(gtype) else") {
p ("return nil")
}
p ("var content: \(typeName).ContentType = \(typeName).zero")
p ("from.toType(.\(gtype), dest: &content)")
p ("// Replicate the constructor, because of a lame Swift requirement")
p ("var args: [UnsafeRawPointer?] = [UnsafeRawPointer(&content)]")
p ("\(typeName).constructor1 (&content, &args)")
}
} else {
b ("public init? (_ from: Variant)") {
b ("guard from.gtype == .\(gtype) else") {
p ("return nil")
}
p ("var v = \(bc.name)()")
p ("from.toType(.\(gtype), dest: &v)")
p ("self.init (from: v)")
}
}
p ("/// Wraps this \(typeName) into a Variant")
b ("public func toVariant () -> Variant ") {
p ("Variant (self)")
}

if let members = bc.members {
if bc.name == "Color" {
p ("public var red: Float")
13 changes: 11 additions & 2 deletions Generator/Generator/ClassGen.swift
Original file line number Diff line number Diff line change
@@ -428,7 +428,7 @@ func generateProperties (cdef: JGodotExtensionAPIClass, docClass: DocClass?, _ p
}
}

#if false
#if true
var okList = [ "RefCounted", "Node", "Sprite2D", "Node2D", "CanvasItem", "Object", "String", "StringName", "AStar2D", "Material", "Camera3D", "Node3D", "ProjectSettings", "MeshInstance3D", "BoxMesh", "SceneTree", "Window" ]
#else
var okList: [String] = []
@@ -505,7 +505,16 @@ func generateClasses (values: [JGodotExtensionAPIClass], outputDir: String) {

if cdef.name == "Object" {
p ("public func toVariant () -> Variant { Variant (self) }")
}
}
p ("/// Instantiates the object from a variant - it is an error if it happens for user-derived types")
p ("/// While users types must implement this constructor, they can safely just call fatalError, it")
p ("/// will never be invoked.")
b ("public required init? (_ variant: Variant)") {
p ("guard variant.gtype == .object else { return nil }")
p ("var handle = UnsafeMutableRawPointer(bitPattern: 0)")
p ("variant.toType(.object, dest: &handle)")
p ("super.init (nativeHandle: handle!)")
}
let fastInitOverrides = cdef.inherits != nil ? "override " : ""

b ("internal \(fastInitOverrides)init (fast: Bool)") {
43 changes: 43 additions & 0 deletions Generator/Generator/TypeHelpers.swift
Original file line number Diff line number Diff line change
@@ -158,6 +158,49 @@ func mapTypeNameDoc (_ name: String) -> String {
return mapTypeName (name)
}

func gtypeFromTypeName (_ name: String) -> String {
switch name {
case "Nil": return "`nil`"
case "Bool": return "bool"
case "Int": return "int"
case "String": return "string"
case "Vector2": return "vector2"
case "Vector2i": return "vector2i"
case "Rect2": return "rect2"
case "Rect2i": return "rect2i"
case "Vector3": return "vector3"
case "Vector3i": return "vector3i"
case "Transform2D": return "transform2d"
case "Vector4": return "vector4"
case "Vector4i": return "vector4i"
case "Plane": return "plane"
case "Quaternion": return "quaternion"
case "AABB": return "aabb"
case "Basis": return "basis"
case "Transform3D": return "transform3d"
case "Projection": return "projection"
case "Color": return "color"
case "StringName": return "stringName"
case "NodePath": return "nodePath"
case "RID": return "rid"
case "Object": return "object"
case "Callable": return "callable"
case "Signal": return "signal"
case "Dictionary": return "dictionary"
case "Array": return "array"
case "PackedByteArray": return "packedByteArray"
case "PackedInt32Array": return "packedInt32Array"
case "PackedInt64Array": return "packedInt64Array"
case "PackedFloat32Array": return "packedFloat32Array"
case "PackedFloat64Array": return "packedFloat64Array"
case "PackedStringArray": return "packedStringArray"
case "PackedVector2Array": return "packedVector2Array"
case "PackedVector3Array": return "packedVector3Array"
case "PackedColorArray": return "packedColorArray"
default:
fatalError("Unknonw data type: \(name)")
}
}
struct SimpleType: TypeWithMeta {
var type: String
var meta: JGodotArgumentMeta?
101 changes: 21 additions & 80 deletions Sources/SwiftGodot/Core/GodotCollection.swift
Original file line number Diff line number Diff line change
@@ -11,10 +11,18 @@ import Foundation
/// Protocol implemented by the built-in classes in Godot to allow to be wrapped in a ``Variant``
public protocol GodotVariant {
func toVariant () -> Variant
init? (_ fromVariant: Variant)
}

/// This represents a typed array of one of the built-in types from Godot
public class GodotCollection<T:GodotVariant>: GArray, Collection {
enum CacheState {
case notProbed
case isObject
case notObject
}
var cache_isObject: CacheState = .notProbed
typealias GodotVariantType = T
override init (content: Int64) {
super.init (content: content)
}
@@ -27,95 +35,28 @@ public class GodotCollection<T:GodotVariant>: GArray, Collection {
//gi.array_set_typed (&content, GDExtensionVariantType (UInt32(T.variantType.rawValue)), &name.content, &variant.content)
}

public required init? (_ variant: Variant) {
super.init (variant)
}

// If I make this optional, I am told I need to implement an internal _read method
public subscript (index: Index) -> Iterator.Element {
get {
let v = super [index]
switch v.gtype {
case .object:
var v = super [index]
if cache_isObject == .notProbed {
cache_isObject = v.gtype == .object ? .isObject : .notObject
}
if cache_isObject == .isObject {
var handle = UnsafeMutableRawPointer(bitPattern: 0)
v.toType(.object, dest: &handle)
if let o = lookupLiveObject(handleAddress: handle!) as? T {
return o
}
case .nil:
return Nil () as! T
case .bool:
return Bool (v) as! T
case .int:
return Int (v) as! T
case .float:
return Float(v) as! T
case .string:
return GString(v) as! T
case .vector2:
return Vector2(v) as! T
case .vector2i:
return Vector2i(v) as! T
case .rect2:
return Rect2(v) as! T
case .rect2i:
return Rect2i(v) as! T
case .vector3:
return Vector3(v) as! T
case .vector3i:
return Vector3i(v) as! T
case .transform2d:
return Transform2D(v) as! T
case .vector4:
return Vector4(v) as! T
case .vector4i:
return Vector4i(v) as! T
case .plane:
return Plane(v) as! T
case .quaternion:
return Quaternion(v) as! T
case .aabb:
return AABB(v) as! T
case .basis:
return Basis(v) as! T
case .transform3d:
return Transform3D(v) as! T
case .projection:
return Projection(v) as! T
case .color:
return Color(v) as! T
case .stringName:
return StringName(v) as! T
case .nodePath:
return NodePath(v) as! T
case .rid:
return RID(v) as! T
case .callable:
return Callable(v) as! T
case .signal:
return Signal(v) as! T
case .dictionary:
return Dictionary(v) as! T
case .array:
return GArray(v) as! T
case .packedByteArray:
return PackedByteArray (v) as! T
case .packedInt32Array:
return PackedInt32Array (v) as! T
case .packedInt64Array:
return PackedInt64Array (v) as! T
case .packedFloat32Array:
return PackedFloat32Array (v) as! T
case .packedFloat64Array:
return PackedFloat64Array (v) as! T
case .packedStringArray:
return PackedStringArray (v) as! T
case .packedVector2Array:
return PackedVector2Array (v) as! T
case .packedVector3Array:
return PackedVector3Array (v) as! T
case .packedColorArray:
return PackedColorArray (v) as! T
default:
fatalError()
if let o = lookupFrameworkObject(handleAddress: handle!) as? T {
return o
}
}
fatalError ("This collection contained objects of a different type")
return T.init (v)!
}
set {
super [index] = newValue.toVariant()
19 changes: 14 additions & 5 deletions Sources/SwiftGodot/Core/Wrapped.swift
Original file line number Diff line number Diff line change
@@ -101,7 +101,7 @@ open class Wrapped: Equatable, Identifiable {
/// subclasses will have a diffrent name than the subclass
internal init (name: StringName) {
let v = gi.classdb_construct_object (UnsafeRawPointer (&name.content))

if let r = UnsafeRawPointer (v) {
handle = r
let retain = Unmanaged.passRetained(self)
@@ -111,13 +111,13 @@ open class Wrapped: Equatable, Identifiable {
let thisTypeName = StringName (String (describing: Swift.type(of: self)))
let frameworkType = thisTypeName == name

print ("SWIFT: Wrapped(StringName) at \(handle), this is a class of type: \(Swift.type(of: self)) and it is: \(frameworkType ? "Builtin" : "User defined")")
//print ("SWIFT: Wrapped(StringName) at \(handle) with retain=\(retain.toOpaque()), this is a class of type: \(Swift.type(of: self)) and it is: \(frameworkType ? "Builtin" : "User defined")")

// This I believe should only be set for user subclasses, and not anything else.
if frameworkType {
print ("SWIFT: Skipping object registration, this is a framework type")
//print ("SWIFT: Skipping object registration, this is a framework type")
} else {
print ("SWIFT: Registering instance with Godot")
//print ("SWIFT: Registering instance with Godot")
gi.object_set_instance (UnsafeMutableRawPointer (mutating: handle),
UnsafeRawPointer (&thisTypeName.content), retain.toOpaque())
}
@@ -139,7 +139,7 @@ open class Wrapped: Equatable, Identifiable {

func register<T:Wrapped> (type name: StringName, parent: StringName, type: T.Type) {
guard let wt = type as? Wrapped.Type else {
print ("The provided type should be a subclass of SwiftGodot.Wrapped type")
print ("SWIFT: The provided type should be a subclass of SwiftGodot.Wrapped type")
return
}

@@ -213,6 +213,15 @@ func lookupLiveObject (handleAddress: UnsafeRawPointer) -> Wrapped? {
return liveSubtypedObjects [handleAddress]
}

///
/// Looks into the liveSubtypedObjects table if we have an object registered for it,
/// and if we do, we returned that existing instance.
///
/// We are surfacing this, so that when we recreate an object resurfaced in a collection
/// we do not get the base type, but the most derived one
func lookupFrameworkObject (handleAddress: UnsafeRawPointer) -> Wrapped? {
return liveFrameworkObjects [handleAddress]
}
///
/// This one is invoked by Godot when an instance of one of our types is created, and we need
/// to instantiate it. Notice that this is different that direct instantiation from our API
Loading

0 comments on commit 6754671

Please sign in to comment.