# Copyright 2018 Xored Software, Inc. import tables, typetraits, macros, unicode, strutils, sets, options import gdnativeapi import core/godotcoretypes, core/godotbase import core/vector2, core/rect2, core/vector3, core/transform2d, core/planes, core/quats, core/aabb, core/basis, core/transforms, core/colors, core/nodepaths, core/rids, core/dictionaries, core/arrays, core/poolarrays, core/variants import godotinternal ## This module defines ``NimGodotObject`` and ``toVariant``/``fromVariant`` ## converters for Nim types. The converters are used by ## `gdobj `_ macro to import/export ## values from/to Godot (editor, GDScript). ## ## You can also allow conversion of any custom type ``MyType`` by implementing: ## ## .. code-block:: nim ## proc godotTypeInfo*(T: typedesc[MyType]): GodotTypeInfo ## proc toVariant*(self: MyType): Variant ## proc fromVariant*(self: var MyType, val: Variant): ConversionResult ## ## Implementing ``godotTypeInfo`` is optional and is necessary only if you want ## the type to be editable from the editor. type NimGodotObject* = ref object of RootObj ## The base type all Godot types inherit from. ## Manages lifecycle of the wrapped ``GodotObject``. godotObject: ptr GodotObject linkedObjectPtr: pointer ## Wrapper around native object that is the container of the Nim "script" ## This is needed for `of` checks and `as` conversions to work as ## expected. For example, Nim type may inherit from ``Spatial``, but the ## script is attached to ``Particles``. In this case conversion to ## ``Particles`` is valid, but Nim type system is not aware of that. ## This works in both directions - for linked native object this ## reference points to Nim object. ## This is stored as a raw pointer to avoid reference cycles and therefore ## improve GC performance. isRef*: bool isFinalized: bool isNative: bool ConversionResult* {.pure.} = enum ## Conversion result to return from ``fromVariant`` procedure. OK, TypeError, ## Type mismatch RangeError ## Types match, but the value is out of range. ## Mainly used for numeric (ordinal) types. SomeUnsignedInt = uint8 or uint16 or uint32 or uint64 or uint or cuint SomeSignedInt = int8 or int16 or int32 or int64 or int or cint SomeFloat = float32 or float64 or cfloat SomeGodot = bool or Vector2 or Rect2 or Vector3 or Transform2D or Plane or Quat or AABB or Basis or Transform or Color or NodePath or RID or GodotObject or Dictionary or Array or PoolByteArray or PoolIntArray or PoolRealArray or PoolStringArray or PoolVector2Array or PoolVector3Array or PoolColorArray or GodotString SomeGodotOrNum = SomeGodot or SomeSignedInt or SomeUnsignedInt or SomeFloat CallError* = object of Exception ## Raised by wrappers in case of an incorrect dynamic invocation. ## For example, if incorrect number of arguments were passed or they had ## unexpected types. err*: VariantCallError ## The error as returned by Godot ObjectInfo = object constructor: proc(): NimGodotObject {.gcsafe, nimcall.} baseNativeClass: cstring isNative: bool isRef: bool GodotTypeInfo* = object ## Type information provided to Godot editor variantType*: VariantType hint*: GodotPropertyHint hintStr*: string ## Format depends on the type and the hint. ## For example, for Enum ``hint`` this is a comma separated list of ## values. ## See documentation of ``GodotPropertyHint`` for description of formats. FNV1Hash = uint32 include "internal/backwardcompat.inc.nim" proc isFinalized*(obj: NimGodotObject): bool {.inline.} = obj.isFinalized var classRegistry {.threadvar.}: TableRef[FNV1Hash, ObjectInfo] var classRegistryStatic* {.compileTime.}: TableRef[FNV1Hash, ObjectInfo] ## Compile-time variable used for implementation of several procedures ## and macros static: classRegistryStatic = newTable[FNV1Hash, ObjectInfo]() var nativeClasses {.compileTime.} = newSeq[string]() var refClasses* {.compileTime.} = newSeq[string]() template initFNV1Hash(hash: var FNV1Hash) = hash = 0x811c9dc5'u32 template appendFNV1Hash(hash: var FNV1Hash, val: uint8) = block: let u64hash = hash.uint64 hash = (( u64hash + (u64hash shl 1'u64) + (u64hash shl 4'u64) + (u64hash shl 7'u64) + (u64hash shl 8'u64) + (u64hash shl 24'u64)) and 0xFFFFFFFF'u32).uint32 xor val {.push stackTrace:off.} proc lsb(c: ptr cwchar_t): char {.noinit, inline.} = {.emit: [result, " = (char)(", c[]," & 0xFF);"]} {.pop.} proc fnv1Hash(godotClassName: GodotString): FNV1Hash = var charsCount = godotClassName.len let charsPtr = godotClassName.dataPtr if charsCount > 2 and charsPtr.offset(charsCount - 2).lsb == 'S' and charsPtr.offset(charsCount - 1).lsb == 'W': charsCount -= 2 initFNV1Hash(result) for i in 0..= charsCount: break let firstByte = uint8(rune.uint64 and 0xFF'u64) appendFNV1Hash(result, firstByte) # We don't really need to calculate hash of other bytes, # since we only use hashes of ASCII strings inc i proc getClassName*(o: NimGodotObject): string = o.godotObject.getClassName() var unreferenceMethodBind {.threadvar.}: ptr GodotMethodBind proc unreference(o: ptr GodotObject): bool = if isNil(unreferenceMethodBind): unreferenceMethodBind = getMethod(cstring"Reference", cstring"unreference") unreferenceMethodBind.ptrCall(o, nil, addr(result)) var referenceMethodBind {.threadvar.}: ptr GodotMethodBind proc reference(o: ptr GodotObject): bool {.discardable.} = if isNil(referenceMethodBind): referenceMethodBind = getMethod(cstring"Reference", cstring"reference") referenceMethodBind.ptrCall(o, nil, addr result) var initRefMethodBind {.threadvar.}: ptr GodotMethodBind proc initRef(o: ptr GodotObject): bool {.discardable.} = if isNil(initRefMethodBind): initRefMethodBind = getMethod(cstring"Reference", cstring"init_ref") initRefMethodBind.ptrCall(o, nil, addr result) proc deinit*(obj: NimGodotObject) = ## Destroy the object. You only need to call this for objects not inherited ## from Reference, where manual lifecycle control is necessary. assert(not obj.godotObject.isNil) obj.godotObject.deinit() obj.godotObject = nil proc linkedObject(obj: NimGodotObject): NimGodotObject {.inline.} = cast[NimGodotObject](obj.linkedObjectPtr) proc nimGodotObjectFinalizer*[T: NimGodotObject](obj: T) = if obj.godotObject.isNil or obj.isNative: return # important to set it before so that ``unreference`` is aware obj.isFinalized = true if (obj.isRef or not obj.linkedObject.isNil and obj.linkedObject.isRef) and obj.godotObject.unreference(): obj.deinit() macro baseNativeType(T: typedesc): cstring = var t = getType(getType(T)[1][1]) var baseT: string while true: let typeName = ($t[1][1]).split(':')[0] if typeName in nativeClasses: baseT = typeName break if typeName == "NimGodotObject": break t = getType(t[1][1]) if baseT.len == 0: result = newNilLit() else: let rStr = newNimNode(nnkRStrLit) rStr.strVal = baseT result = newNimNode(nnkCallStrLit).add(ident("cstring"), rStr) proc inherits(t: NimNode, parent: string): bool {.compileTime.} = var curT = t while true: let typeName = ($curT[1][1]).split(':')[0] if typeName == parent: return true if typeName == "NimGodotObject": break curT = getType(curT[1][1]) macro isReference(T: typedesc): bool = result = if inherits(getType(T), "Reference"): ident("true") else: ident("false") macro isResource(T: typedesc): bool = result = if inherits(getType(T), "Resource"): ident("true") else: ident("false") template registerClass*(T: typedesc; godotClassName: string or cstring, native: bool) = ## Registers the specified Godot type. ## Used by ``gdobj`` macro and `godotapigen `_. if classRegistry.isNil: classRegistry = newTable[FNV1Hash, ObjectInfo]() let constructor = proc(): NimGodotObject = var t: T new(t, nimGodotObjectFinalizer[T]) result = t const base = baseNativeType(T) const isRef: bool = isReference(T) let objInfo = ObjectInfo( constructor: constructor, baseNativeClass: base, isNative: native, isRef: isRef ) classRegistry[fnv1Hash($godotClassName)] = objInfo static: let objInfoStatic = ObjectInfo( baseNativeClass: base, isNative: native, isRef: isRef, ) let nameHash = fnv1Hash($godotClassName) if not classRegistryStatic.contains(nameHash): classRegistryStatic[nameHash] = objInfoStatic elif not endsWith($godotClassName, "SW"): # For simplicity we assume that all class names must have # different hashes # If this exception is ever raised, I guess, we should # implement a proper collision resolving raise newException(Exception, "Hash collision " & $godotClassName) when isRef: static: refClasses.add(T.name) when native: static: nativeClasses.add(T.name) proc newNimGodotObject[T: NimGodotObject]( godotObject: ptr GodotObject, godotClassName: GodotString, noRef: bool): T = assert(not classRegistry.isNil) assert(not godotObject.isNil) let objInfo = classRegistry.getOrDefault(fnv1Hash(godotClassName)) if objInfo.constructor.isNil: printError("Nim constructor not found for class " & $godotClassName) else: result = T(objInfo.constructor()) result.godotObject = godotObject result.isRef = objInfo.isRef if not noRef and objInfo.isRef: result.godotObject.reference() proc asNimGodotObject*[T: NimGodotObject]( godotObject: ptr GodotObject, forceNativeObject, noRef: bool = false): T = ## Wraps ``godotObject`` into Nim type ``T``. ## This is used by `godotapigen `_ and should rarely be ## used by anything else. if godotObject.isNil: return nil let userDataPtr = godotObject.getUserData() if not userDataPtr.isNil and not forceNativeObject: result = cast[T](userDataPtr) if result.godotObject != godotObject: # Could be data from other bindings result = nil if result.isNil: var classNameStr = godotObject.getClassNameRaw() result = newNimGodotObject[T]( godotObject, classNameStr, forceNativeObject or noRef) deinit(classNameStr) proc newVariant*(obj: NimGodotObject): Variant {.inline.} = newVariant(obj.godotObject) proc asObject*[T: NimGodotObject](v: Variant): T {.inline.} = ## Converts ``v`` to object of type ``T``. ## Returns ``nil`` if the conversion cannot be performed ## (``v``'s type is not an Object or it's an object of an incompatible type) asNimGodotObject[T](v.asGodotObject()) proc asObject*(v: Variant, T: typedesc[NimGodotObject]): T {.inline.} = ## Converts ``v`` to object of type ``T``. ## Returns ``nil`` if the conversion cannot be performed ## (``v``'s type is not an Object or it's an object of an incompatible type) asNimGodotObject[T](v.asGodotObject()) proc `as`*[T: NimGodotObject](obj: NimGodotObject, t: typedesc[T]): T = ## Converts the ``obj`` into the specified type. ## Returns ``nil`` if the conversion cannot be performed ## (the ``obj`` is not of the type ``T``) ## ## This can be used either in dot notation (``node.as(Button)``) or ## infix notation (``node as Button``). if obj.isNil: return nil if system.`of`(obj, T): result = T(obj) elif not obj.linkedObject.isNil and system.`of`(obj.linkedObject, T): result = T(obj.linkedObject) else: result = nil proc `of`*[T1, T2: NimGodotObject](obj: T1, t: typedesc[T2]): bool {.inline.} = ## Godot-specific inheritance check. not obj.isNil and (system.`of`(obj, T2) or system.`of`(obj.linkedObject, T2)) proc newRStrLit(s: string): NimNode {.compileTime.} = result = newNimNode(nnkRStrLit) result.strVal = s macro toGodotName(T: typedesc): untyped = if T is GodotString or T is string: newStrLitNode"String" elif T is SomeFloat: newStrLitNode"float" elif T is SomeUnsignedInt or T is SomeSignedInt: newStrLitNode"int" else: let nameStr = ((T.getType()[1][1].strVal).split(':')[0]) case nameStr: of "File", "Directory", "Thread", "Mutex", "Semaphore": newStrLitNode("_" & nameStr) else: newStrLitNode(nameStr) macro asCString(s: static[string]): cstring = result = newNimNode(nnkCallStrLit).add( ident("cstring"), newRStrLit(s)) proc getSingleton*[T: NimGodotObject](): T = ## Returns singleton of type ``T``. Normally, this should not be used, ## because `godotapigen `_ wraps singleton methods so that ## singleton objects don't have to be provided as parameters. const godotName = asCString(toGodotName(T)) let singleton = getGodotSingleton(godotName) if singleton.isNil: printError("Tried to get non-existing singleton of type " & $godotName) else: result = asNimGodotObject[T](singleton) var gdNativeLibraryObj: ptr GodotObject var setClassNameMethod: ptr GodotMethodBind var setLibraryMethod: ptr GodotMethodBind var newMethod: ptr GodotMethodBind type PtrCallArgType = ptr array[MAX_ARG_COUNT, pointer] proc newOwnObj[T: NimGodotObject](name: cstring): T = let newNativeScript = getClassConstructor(cstring"NativeScript")() if setClassNameMethod.isNil: setClassNameMethod = getMethod(cstring"NativeScript", cstring"set_class_name") if setLibraryMethod.isNil: setLibraryMethod = getMethod(cstring"NativeScript", cstring"set_library") if newMethod.isNil: newMethod = getMethod(cstring"NativeScript", cstring"new") var godotStrName = name.toGodotString() var argPtr: pointer = addr godotStrName setClassNameMethod.ptrCall( newNativeScript, cast[PtrCallArgType](addr argPtr), nil) deinit(godotStrName) setLibraryMethod.ptrCall( newNativeScript, cast[PtrCallArgType](addr gdNativeLibraryObj), nil) var err: VariantCallError var ret = newMethod.call(newNativeScript, nil, 0, err) if err.error != VariantCallErrorType.OK: printError("Failed to invoke 'new' on NativeScript for " & $name) elif ret.getType() != VariantType.Object: printError("Expected that NativeScript::new returns Object, " & "but it returned: " & $ret.getType()) else: result = asNimGodotObject[T](ret.asGodotObject()) if result.isRef: result.isFinalized = true result.godotObject.reference() result.isFinalized = false ret.deinit() proc gdnew*[T: NimGodotObject](): T = ## Instantiates new object of type ``T``. const godotName = toGodotName(T) const cGodotName = asCString(godotName) const objInfo = classRegistryStatic[fnv1Hash(godotName)] when objInfo.isNative: let godotObject = getClassConstructor(cGodotName)() new(result, nimGodotObjectFinalizer[T]) result.godotObject = godotObject when objInfo.isRef: godotObject.initRef() result.isRef = true else: result = newOwnObj[T](cGodotName) proc newCallError*(err: VariantCallError): ref CallError = ## Instantiates ``CallError`` from Godot ``err``. let msg = case err.error: of VariantCallErrorType.OK, VariantCallErrorType.InvalidMethod, VariantCallErrorType.TooManyArguments, VariantCallErrorType.TooFewArguments, VariantCallErrorType.InstanceIsNull: $err.error of VariantCallErrorType.InvalidArgument: "invalid argument at position " & $err.argument & ": expected " & $err.expected result = newException(CallError, msg) result.err = err proc newConversionError*(err: ConversionResult): ref ValueError = ## Instantiates error raised by `godotapigen `_ ## generated code in case of a conversion error. let msg = case err: of ConversionResult.TypeError: "Failed to convert the return value into Nim type" of ConversionResult.RangeError: "The returned value was out of bounds" of ConversionResult.OK: "OK" result = newException(ValueError, msg) proc setGodotObject*(nimObj: NimGodotObject, obj: ptr GodotObject) {.inline.} = ## Used from Godot constructor produced by ``gdobj`` macro. Do not call. assert(not obj.isNil) assert(nimObj.godotObject.isNil) # reassignment is not allowed nimObj.godotObject = obj proc setNativeObject*(nimObj: NimGodotObject, nativeObj: NimGodotObject) {.inline.} = ## Used from Godot constructor produced by ``gdobj`` macro. Do not call. GC_ref(nativeObj) nimObj.linkedObjectPtr = cast[pointer](nativeObj) nativeObj.linkedObjectPtr = cast[pointer](nimObj) nativeObj.isNative = true proc removeGodotObject*(nimObj: NimGodotObject) {.inline.} = ## Used from Godot destructor produced by ``gdobj`` macro. Do not call. GC_unref(nimObj.linkedObject) nimObj.godotObject = nil nimObj.linkedObject.godotObject = nil nimObj.linkedObjectPtr = nil proc `==`*(self, other: NimGodotObject): bool {.inline.} = ## Compares objects by referential equality. if self.isNil and other.isNil: return true if self.isNil != other.isNil: return false result = (self.godotObject == other.godotObject) proc godotObject*(nimObj: NimGodotObject): ptr GodotObject {.inline.} = ## Returns raw poitner to ``GodotObject``. Use only if you know what ## you are doing. nimObj.godotObject proc godotTypeInfo*(T: typedesc[NimGodotObject]): GodotTypeInfo {.inline.} = GodotTypeInfo( variantType: VariantType.Object, hint: when isResource(T): GodotPropertyHint.ResourceType else: GodotPropertyHint.None, hintStr: toGodotName(T) ) proc toVariant*(self: NimGodotObject): Variant {.inline.} = if self.isNil: newVariant() else: newVariant(self.godotObject) proc fromVariant*[T: NimGodotObject](self: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Object: let objPtr = val.asGodotObject() self = asNimGodotObject[T](objPtr) if self.isNil: result = ConversionResult.TypeError elif val.getType() == VariantType.Nil: self = nil else: result = ConversionResult.TypeError proc toVariant*(self: Variant): Variant {.inline.} = if self.isNil: newVariant() else: newVariant(self) proc fromVariant*(self: var Variant, val: Variant): ConversionResult {.inline.} = self = newVariant(val) proc godotTypeInfo*(T: typedesc[SomeGodotOrNum]): GodotTypeInfo {.inline.} = result.variantType = when T is SomeSignedInt or T is SomeUnsignedInt: VariantType.Int elif T is bool: VariantType.Bool elif T is SomeFloat: VariantType.Real elif T is string or T is GodotString: VariantType.String elif T is Vector2: VariantType.Vector2 elif T is Rect2: VariantType.Rect2 elif T is Vector3: VariantType.Vector3 elif T is Transform2D: VariantType.Transform2D elif T is Plane: VariantType.Plane elif T is Quat: VariantType.Quat elif T is AABB: VariantType.AABB elif T is Basis: VariantType.Basis elif T is Transform: VariantType.Transform elif T is Color: VariantType.Color elif T is RID: VariantType.RID elif T is ptr GodotObject: VariantType.Object elif T is NodePath: VariantType.NodePath elif T is Dictionary: VariantType.Dictionary elif T is Array: VariantType.Array elif T is PoolByteArray: VariantType.PoolByteArray elif T is PoolIntArray: VariantType.PoolIntArray elif T is PoolRealArray: VariantType.PoolRealArray elif T is PoolStringArray: VariantType.PoolStringArray elif T is PoolVector2Array: VariantType.PoolVector2Array elif T is PoolVector3Array: VariantType.PoolVector3Array elif T is PoolColorArray: VariantType.PoolColorArray else: VariantType.Nil proc godotTypeInfo*(T: typedesc[range]): VariantType {.inline.} = GodotTypeInfo( variantType: VariantType.Int, hint: GodotPropertyHint.Range, hintStr: $low(T) & "," & $high(T) & ",1" ) proc toVariant*[T: SomeGodotOrNum](val: T): Variant {.inline.} = when val is ref: if val.isNil: newVariant() else: newVariant(val) else: newVariant(val) proc fromVariant*[T: SomeSignedInt or SomeUnsignedInt]( self: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Nil: self = 0 elif val.getType() == VariantType.Int or val.getType() == VariantType.Real: # Real is allowed, because that's what the editor sets for Int values var intVal: (when T is SomeSignedInt: int64 else: uint64) intVal = when T is SomeSignedInt: val.asInt() else: val.asUint() const highT = when T is SomeSignedInt: high(T).int64 else: high(T).uint64 const lowT = when T is SomeSignedInt: low(T).int64 else: low(T).uint64 if intVal > highT or intVal < lowT: result = ConversionResult.RangeError else: self = T(intVal) else: result = ConversionResult.TypeError proc godotTypeInfo*(T: typedesc[enum]): GodotTypeInfo = result = GodotTypeInfo( variantType: VariantType.Int, hint: GodotPropertyHint.Enum, hintStr: "" ) for val in T: if result.hintStr.len > 0: result.hintStr.add(',') result.hintStr.add($val) proc toVariant*[T: enum](self: T): Variant {.inline.} = newVariant(int64(ord(self))) proc fromVariant*[T: enum](self: var T, val: Variant): ConversionResult {.inline.} = var intConv: int64 result = fromVariant(intConv, val) if result == ConversionResult.OK: self = T(intConv) proc fromVariant*[T: SomeFloat](self: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Nil: self = 0 elif val.getType() == VariantType.Real or val.getType() == VariantType.Int: self = T(val.asReal()) else: result = ConversionResult.TypeError proc fromVariant*[T: SomeGodot](self: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Nil: return const typeInfo = godotTypeInfo(T) if typeInfo.variantType != val.getType(): return ConversionResult.TypeError when self is bool: self = val.asBool() elif self is Vector2: self = val.asVector2() elif self is Rect2: self = val.asRect2() elif self is Vector3: self = val.asVector3() elif self is Transform2D: self = val.asTransform2D() elif self is Plane: self = val.asPlane() elif self is Quat: self = val.asQuat() elif self is AABB: self = val.asAABB() elif self is Basis: self = val.asBasis() elif self is Transform: self = val.asTransform() elif self is Color: self = val.asColor() elif self is NodePath: self = val.asNodePath() elif self is RID: self = val.asRID() elif self is ptr GodotObject: self = val.asObject() elif self is Dictionary: self = val.asDictionary() elif self is Array: self = val.asArray() elif self is PoolByteArray: self = val.asPoolByteArray() elif self is PoolIntArray: self = val.asPoolIntArray() elif self is PoolRealArray: self = val.asPoolRealArray() elif self is PoolStringArray: self = val.asPoolStringArray() elif self is PoolVector2Array: self = val.asPoolVector2Array() elif self is PoolVector3Array: self = val.asPoolVector3Array() elif self is PoolColorArray: self = val.asPoolColorArray() elif self is GodotString: self = val.asGodotString() else: # mustn't reach this result = ConversionError.TypeError proc godotTypeInfo*(T: typedesc[string]): GodotTypeInfo {.inline.} = result.variantType = VariantType.String proc toVariant*(s: string): Variant {.inline.} = newVariant(s) proc fromVariant*(s: var string, val: Variant): ConversionResult = if val.getType() == VariantType.String: s = val.asString() elif val.getType() == VariantType.Nil: when (NimMajor, NimMinor, NimPatch) < (0, 19, 0): s = nil else: s = "" else: result = ConversionResult.TypeError template arrTypeInfo(T) = result.variantType = VariantType.Array mixin godotTypeInfo type ItemT = type(( block: var s: T; when T is seq: s[0] else: s[low(s)])) when compiles(godotTypeInfo(ItemT)): let itemTypeInfo = godotTypeInfo(ItemT) result.hintStr = $ord(itemTypeInfo.variantType) result.hintStr.add('/') result.hintStr.add($ord(itemTypeInfo.hint)) result.hintStr.add(':') if not itemTypeInfo.hintStr.isNil: result.hintStr.add(itemTypeInfo.hintStr) proc godotTypeInfo*(T: typedesc[seq]): GodotTypeInfo = arrTypeInfo(T) proc godotTypeInfo*(T: typedesc[array]): GodotTypeInfo = arrTypeInfo(T) template arrayToVariant(s: untyped): Variant = var arr = newArray() mixin toVariant for item in s: arr.add(toVariant(item)) newVariant(arr) proc toVariant*[T](s: seq[T]): Variant = if s.isNil: return newVariant() result = arrayToVariant(s) proc toVariant*[I, T](s: array[I, T]): Variant = result = arrayToVariant(s) proc fromVariant*[T](s: var seq[T], val: Variant): ConversionResult = if val.getType() == VariantType.Nil: s = nil elif val.getType() == VariantType.Array: let arr = val.asArray() var newS = newSeq[T](arr.len) for idx, item in arr: mixin fromVariant let convResult = fromVariant(newS[idx], item) if convResult != ConversionResult.OK: return convResult shallowCopy(s, newS) else: result = ConversionResult.TypeError proc fromVariant*[T: array](s: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Nil: discard elif val.getType() == VariantType.Array: let arr = val.asArray() if s.len != arr.len: return ConversionResult.TypeError var nimIdx = low(s) # may not start from 0 and may even be an enum for item in arr: mixin fromVariant let convResult = fromVariant(s[nimIdx], item) if convResult != ConversionResult.OK: return convResult if nimIdx != high(s): inc nimIdx else: result = ConversionResult.TypeError proc godotTypeInfo*[T](OptT: typedesc[Option[T]]): GodotTypeInfo = when compiles(godotTypeInfo(T)): result.variantType = godotTypeInfo(T) else: result.variantType = VariantType.Nil proc toVariant*[T](option: Option[T]): Variant = if option.isSome(): result = toVariant(option.unsafeGet()) else: result = newVariant() proc fromVariant*[T](option: var Option[T], val: Variant): ConversionResult = if val.getType() == VariantType.Nil: option = none(T) else: var v: T result = fromVariant(v, val) if result == ConversionResult.OK: option = some(result) proc godotTypeInfo*(T: typedesc[Table|TableRef|OrderedTable|OrderedTableRef]): GodotTypeInfo {.inline.} = result.variantType = VariantType.Dictionary proc toVariant*[T: Table or TableRef or OrderedTable or OrderedTableRef](t: T): Variant = when t is ref: if t.isNil: return newVariant() var dict = newDictionary() mixin toVariant for k, v in t.pairs(): dict[toVariant(k)] = toVariant(v) result = newVariant(dict) proc fromVariant*[T: Table or TableRef or OrderedTable or OrderedTableRef](t: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Nil: when t is ref: t = nil elif val.getType() == VariantType.Dictionary: let dict = val.asDictionary() mixin fromVariant when t is Table: t = initTable[type(t.keys()), type(t.values())]() elif t is TableRef: t = newTable[type(t.keys()), type(t.values())]() elif t is OrderedTable: t = initOrderedTable[type(t.keys()), type(t.values())]() else: t = newOrderedTable[type(t.keys()), type(t.values())]() for k, v in dict: var nimKey: type(t.keys()) var nimVal: type(t.values()) let keyResult = fromVariant(nimKey, k) if keyResult != ConversionResult.OK: when t is ref: t = nil return keyResult let valResult = fromVariant(nimVal, v) if valResult != ConversionResult.OK: when t is ref: t = nil return valResult t[nimKey] = nimVal else: result = ConversionResult.TypeError when (NimMajor, NimMinor, NimPatch) < (0, 19, 0): {.emit: """/*TYPESECTION*/ N_LIB_EXPORT N_CDECL(void, NimMain)(void); N_NOINLINE(void, setStackBottom)(void* thestackbottom); """.} var nativeLibHandle: pointer proc getNativeLibHandle*(): pointer = ## Returns NativeScript library handle used to register type information ## in Godot. return nativeLibHandle proc godot_nativescript_init(handle: pointer) {. cdecl, exportc, dynlib.} = nativeLibHandle = handle var stackBottom {.volatile.}: pointer stackBottom = addr(stackBottom) {.emit: """ NimMain(); """.} when (NimMajor, NimMinor, NimPatch) < (0, 19, 0): {.emit: """ setStackBottom((void*)(&`stackBottom`)); """.} else: nimGC_setStackBottom(stackBottom) GC_fullCollect() GC_disable() proc godot_gdnative_init(options: ptr GDNativeInitOptions) {. cdecl, exportc, dynlib.} = gdNativeLibraryObj = options.gdNativeLibrary setGDNativeAPI(options.gdNativeAPIStruct, options) proc godot_gdnative_terminate(options: ptr GDNativeTerminateOptions) {. cdecl, exportc, dynlib.} = if not options[].inEditor or not compileOption("threads"): deallocHeap(runFinalizers = not options[].inEditor, allowGcAfterwards = false) const nimGcStepLengthUs {.intdefine.} = 2000 var idleCallbacks {.threadvar.}: seq[proc () {.closure.}] idleCallbacks = newSeq[proc () {.closure.}]() var isMainThread {.threadvar.}: bool isMainThread = true proc registerFrameCallback*(cb: proc () {.closure.}) = # Registers a callback to be called on the main thread at the end # of each frame. if not isMainThread: const err = cstring"registerFrameIdleCallback is called from non-main thread. Ignoring." godotPrintError(err, cstring"registerFrameCallback", cstring"godotnim.nim", 0) else: idleCallbacks.add(cb) {.push stackTrace: off.} proc godot_nativescript_frame() {.cdecl, exportc, dynlib.} = var stackBottom {.volatile.}: pointer stackBottom = addr(stackBottom) when (NimMajor, NimMinor, NimPatch) < (0, 19, 0): {.emit: """ setStackBottom((void*)(&`stackBottom`)); """.} else: nimGC_setStackBottom(stackBottom) for cb in idleCallbacks: cb() GC_step(nimGcStepLengthUs, true, 0) when not defined(release): onUnhandledException = proc(errorMsg: string) = printError("Unhandled Nim exception: " & errorMsg) proc godot_nativescript_thread_enter() {.cdecl, exportc, dynlib.} = when compileOption("threads"): setupForeignThreadGc() else: const err = cstring"A foreign thread is created, but app is compiled without --threads:on. Bad things will happen if Nim code is invoked from this thread. If you see this warning when running the editor and you don't actually use threads, ignore it." var s = err.toGodotString() godotPrint(s) s.deinit() proc godot_nativescript_thread_exit() {.cdecl, exportc, dynlib.} = when compileOption("threads"): teardownForeignThreadGc() {.pop.} # stackTrace: off