diff --git a/godot.nimble b/godot.nimble index fade4f8..60a1136 100644 --- a/godot.nimble +++ b/godot.nimble @@ -1,4 +1,4 @@ -version = "0.7.15" +version = "0.7.16" author = "Xored Software, Inc." description = "Godot Engine bindings" license = "MIT" diff --git a/godot/gdnativeapi.nim b/godot/gdnativeapi.nim index 3ece69b..b24fc6f 100644 --- a/godot/gdnativeapi.nim +++ b/godot/gdnativeapi.nim @@ -1374,7 +1374,8 @@ type stringNewWithWideString: pointer stringOperatorIndex: pointer stringOperatorIndexConst: pointer - stringWideStr: pointer + stringWideStr: proc (self: GodotString): ptr cwchar_t + {.noconv, raises: [], gcsafe, tags: [], locks: 0.} stringOperatorEqual: proc (self, other: GodotString): bool {.noconv, raises: [], gcsafe, tags: [], locks: 0.} stringOperatorLess: proc (self, other: GodotString): bool @@ -2811,6 +2812,8 @@ type {.noconv, raises: [], gcsafe, tags: [], locks: 0.} stringNewCopy*: proc (dest: var GodotString, src: GodotString) {.noconv, raises: [], gcsafe, tags: [], locks: 0.} + stringWideStr*: proc (self: GodotString): ptr cwchar_t + {.noconv, raises: [], gcsafe, tags: [], locks: 0.} stringOperatorEqual*: proc (self, other: GodotString): bool {.noconv, raises: [], gcsafe, tags: [], locks: 0.} stringOperatorLess*: proc (self, other: GodotString): bool @@ -3481,6 +3484,7 @@ proc setGDNativeAPIInternal(apiStruct: pointer, initOptions: ptr GDNativeInitOpt stringNew stringNewCopy + stringWideStr stringOperatorEqual stringOperatorLess stringOperatorPlus diff --git a/godot/godotapigen.nim b/godot/godotapigen.nim index 707154a..876e770 100644 --- a/godot/godotapigen.nim +++ b/godot/godotapigen.nim @@ -839,7 +839,7 @@ proc typeNameToModuleName(name: string): string = proc newRegisterClassNode(typ: GodotType): PNode = newCall("registerClass", ident(typ.name), - newCStringLit(typ.godotName), + newStrLit(typ.godotName), ident("true") # isNative ) @@ -877,7 +877,7 @@ proc genTypeFile(types: Table[string, GodotType], targetDir: string) = if not typ.shouldExport(types): continue let regNode = newCall("registerClass", ident(typ.name), - newCStringLit(typ.godotName), + newStrLit(typ.godotName), ident("true") # isNative ) godotApiTypesTree.add(regNode) diff --git a/godot/godotinternal.nim b/godot/godotinternal.nim index 0314624..ce83428 100644 --- a/godot/godotinternal.nim +++ b/godot/godotinternal.nim @@ -93,6 +93,11 @@ proc getClassName*(o: ptr GodotObject): string = # There are physics type not known by ClassDB result = result[0..result.len-3] +proc getClassNameRaw*(o: ptr GodotObject): GodotString = + if getClassMethodBind.isNil: + getClassMethodBind = getMethod(cstring"Object", cstring"get_class") + getClassMethodBind.ptrCall(o, nil, addr result) + proc getGodotSingleton*(name: cstring): ptr GodotObject {.inline.} = getGDNativeAPI().globalGetSingleton(name) diff --git a/godot/internal/godotinternaltypes.nim b/godot/internal/godotinternaltypes.nim index 192b6cd..8474af8 100644 --- a/godot/internal/godotinternaltypes.nim +++ b/godot/internal/godotinternaltypes.nim @@ -66,6 +66,8 @@ type GodotString* {.byref.} = object data: array[sizeof(int), byte] + cwchar_t* {.importc: "wchar_t", nodecl.} = object + GodotCharString* {.byref.} = object data: array[sizeof(int), byte] @@ -305,3 +307,6 @@ type GodotClassConstructor* = proc (): ptr GodotObject {. noconv, gcsafe, locks: 0, raises: [], tags: [].} + +template offset*[T](p: ptr T, offset: int): ptr T = + cast[ptr T](cast[ByteAddress](p) +% (offset * sizeof(T))) \ No newline at end of file diff --git a/godot/internal/godotpoolarrays.nim b/godot/internal/godotpoolarrays.nim index fb80ad0..e03e1ff 100644 --- a/godot/internal/godotpoolarrays.nim +++ b/godot/internal/godotpoolarrays.nim @@ -3,9 +3,6 @@ import godotinternaltypes, gdnativeapi import core.godotcoretypes -proc offset[T](p: ptr T, offset: int): ptr T {.inline.} = - cast[ptr T](cast[ByteAddress](p) +% (offset * sizeof(T))) - template genPoolArrayAPI(ArrayT, initIdent, DataT, newProc, newCopyProc, newWithArrayProc, appendProc, appendArrayProc, insertProc, invertProc, pushBackProc, diff --git a/godot/internal/godotstrings.nim b/godot/internal/godotstrings.nim index 17443cb..be18df0 100644 --- a/godot/internal/godotstrings.nim +++ b/godot/internal/godotstrings.nim @@ -22,6 +22,12 @@ proc `&`*(self, b: GodotString): GodotString {.inline.} = proc deinit*(self: var GodotString) {.inline.} = getGDNativeAPI().stringDestroy(self) +proc len*(self: GodotString): cint {.inline.} = + getGDNativeAPI().stringLength(self) + +proc dataPtr*(self: GodotString): ptr cwchar_t {.inline.} = + getGDNativeAPI().stringWideStr(self) + proc `$`*(self: GodotString): string = ## Converts the ``GodotString`` into Nim string var charStr = getGDNativeAPI().stringUtf8(self) diff --git a/godot/nim/godotmacros.nim b/godot/nim/godotmacros.nim index 3f1001b..352925a 100644 --- a/godot/nim/godotmacros.nim +++ b/godot/nim/godotmacros.nim @@ -508,7 +508,7 @@ proc genType(obj: ObjectDecl): NimNode {.compileTime.} = # Register Godot object let parentName = if obj.parentName.isNil: newStrLitNode("Object") else: newStrLitNode(obj.parentName) - let classNameLit = newCStringLit(obj.name) + let classNameLit = newStrLitNode(obj.name) let classNameIdent = ident(obj.name) let isRef: bool = if obj.parentName.isNil: false else: obj.parentName in refClasses diff --git a/godot/nim/godotnim.nim b/godot/nim/godotnim.nim index 4702a42..df682ca 100644 --- a/godot/nim/godotnim.nim +++ b/godot/nim/godotnim.nim @@ -1,6 +1,6 @@ # Copyright 2018 Xored Software, Inc. -import tables, typetraits, macros +import tables, typetraits, macros, unicode import gdnativeapi import core.godotcoretypes, core.godotbase import core.vector2, core.rect2, @@ -89,21 +89,71 @@ type ## values. ## See documentation of ``GodotPropertyHint`` for description of formats. + FNV1Hash = uint32 + proc isFinalized*(obj: NimGodotObject): bool {.inline.} = obj.isFinalized -var classRegistry {.threadvar.}: TableRef[cstring, ObjectInfo] -var classRegistryStatic* {.compileTime.}: TableRef[cstring, ObjectInfo] +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[cstring, ObjectInfo]() + classRegistryStatic = newTable[FNV1Hash, ObjectInfo]() static: import sets, strutils 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)).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() @@ -179,12 +229,12 @@ macro isResource(T: typedesc): bool = result = if inherits(getType(T), "Resource"): ident("true") else: ident("false") -template registerClass*(T: typedesc; godotClassName: cstring, +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[cstring, ObjectInfo]() + classRegistry = newTable[FNV1Hash, ObjectInfo]() let constructor = proc(): NimGodotObject = var t: T new(t, nimGodotObjectFinalizer[T]) @@ -198,14 +248,22 @@ template registerClass*(T: typedesc; godotClassName: cstring, isNative: native, isRef: isRef ) - classRegistry[godotClassName] = objInfo + classRegistry[fnv1Hash($godotClassName)] = objInfo static: let objInfoStatic = ObjectInfo( baseNativeClass: base, isNative: native, isRef: isRef, ) - classRegistryStatic[godotClassName] = objInfoStatic + 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) @@ -214,10 +272,10 @@ template registerClass*(T: typedesc; godotClassName: cstring, nativeClasses.add(T.name) proc newNimGodotObject[T: NimGodotObject]( - godotObject: ptr GodotObject, godotClassName: cstring, noRef: bool): T = + godotObject: ptr GodotObject, godotClassName: GodotString, noRef: bool): T = assert(not classRegistry.isNil) assert(not godotObject.isNil) - let objInfo = classRegistry.getOrDefault(godotClassName) + let objInfo = classRegistry.getOrDefault(fnv1Hash(godotClassName)) if objInfo.constructor.isNil: printError("Nim constructor not found for class " & $godotClassName) else: @@ -240,9 +298,11 @@ proc asNimGodotObject*[T: NimGodotObject]( # Could be data from other bindings result = nil if result.isNil: + var classNameStr = godotObject.getClassNameRaw() result = newNimGodotObject[T]( - godotObject, cstring(godotObject.getClassName()), + godotObject, classNameStr, forceNativeObject or noRef) + deinit(classNameStr) proc newVariant*(obj: NimGodotObject): Variant {.inline.} = newVariant(obj.godotObject) @@ -363,17 +423,18 @@ proc newOwnObj[T: NimGodotObject](name: cstring): T = proc gdnew*[T: NimGodotObject](): T = ## Instantiates new object of type ``T``. - const godotName = asCString(toGodotName(T)) - const objInfo = classRegistryStatic[godotName] + const godotName = toGodotName(T) + const cGodotName = asCString(godotName) + const objInfo = classRegistryStatic[fnv1Hash(godotName)] when objInfo.isNative: - let godotObject = getClassConstructor(godotName)() + let godotObject = getClassConstructor(cGodotName)() new(result, nimGodotObjectFinalizer[T]) result.godotObject = godotObject when objInfo.isRef: godotObject.initRef() result.isRef = true else: - result = newOwnObj[T](godotName) + result = newOwnObj[T](cGodotName) proc newCallError*(err: VariantCallError): ref CallError = ## Instantiates ``CallError`` from Godot ``err``.