Make classRegistry use hashes instead of strings as keys for faster lookup

Up version to 0.7.16
This commit is contained in:
Evgeny Zuev 2018-08-28 14:05:33 +07:00 • committed by Ruslan Mustakov
commit ceaf66a34c
9 changed files with 101 additions and 23 deletions

View file

@ -1,4 +1,4 @@
version = "0.7.15" version = "0.7.16"
author = "Xored Software, Inc." author = "Xored Software, Inc."
description = "Godot Engine bindings" description = "Godot Engine bindings"
license = "MIT" license = "MIT"

View file

@ -1374,7 +1374,8 @@ type
stringNewWithWideString: pointer stringNewWithWideString: pointer
stringOperatorIndex: pointer stringOperatorIndex: pointer
stringOperatorIndexConst: pointer stringOperatorIndexConst: pointer
stringWideStr: pointer stringWideStr: proc (self: GodotString): ptr cwchar_t
{.noconv, raises: [], gcsafe, tags: [], locks: 0.}
stringOperatorEqual: proc (self, other: GodotString): bool stringOperatorEqual: proc (self, other: GodotString): bool
{.noconv, raises: [], gcsafe, tags: [], locks: 0.} {.noconv, raises: [], gcsafe, tags: [], locks: 0.}
stringOperatorLess: proc (self, other: GodotString): bool stringOperatorLess: proc (self, other: GodotString): bool
@ -2811,6 +2812,8 @@ type
{.noconv, raises: [], gcsafe, tags: [], locks: 0.} {.noconv, raises: [], gcsafe, tags: [], locks: 0.}
stringNewCopy*: proc (dest: var GodotString, src: GodotString) stringNewCopy*: proc (dest: var GodotString, src: GodotString)
{.noconv, raises: [], gcsafe, tags: [], locks: 0.} {.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 stringOperatorEqual*: proc (self, other: GodotString): bool
{.noconv, raises: [], gcsafe, tags: [], locks: 0.} {.noconv, raises: [], gcsafe, tags: [], locks: 0.}
stringOperatorLess*: proc (self, other: GodotString): bool stringOperatorLess*: proc (self, other: GodotString): bool
@ -3481,6 +3484,7 @@ proc setGDNativeAPIInternal(apiStruct: pointer, initOptions: ptr GDNativeInitOpt
stringNew stringNew
stringNewCopy stringNewCopy
stringWideStr
stringOperatorEqual stringOperatorEqual
stringOperatorLess stringOperatorLess
stringOperatorPlus stringOperatorPlus

View file

@ -839,7 +839,7 @@ proc typeNameToModuleName(name: string): string =
proc newRegisterClassNode(typ: GodotType): PNode = proc newRegisterClassNode(typ: GodotType): PNode =
newCall("registerClass", newCall("registerClass",
ident(typ.name), ident(typ.name),
newCStringLit(typ.godotName), newStrLit(typ.godotName),
ident("true") # isNative ident("true") # isNative
) )
@ -877,7 +877,7 @@ proc genTypeFile(types: Table[string, GodotType], targetDir: string) =
if not typ.shouldExport(types): continue if not typ.shouldExport(types): continue
let regNode = newCall("registerClass", let regNode = newCall("registerClass",
ident(typ.name), ident(typ.name),
newCStringLit(typ.godotName), newStrLit(typ.godotName),
ident("true") # isNative ident("true") # isNative
) )
godotApiTypesTree.add(regNode) godotApiTypesTree.add(regNode)

View file

@ -93,6 +93,11 @@ proc getClassName*(o: ptr GodotObject): string =
# There are physics type not known by ClassDB # There are physics type not known by ClassDB
result = result[0..result.len-3] 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.} = proc getGodotSingleton*(name: cstring): ptr GodotObject {.inline.} =
getGDNativeAPI().globalGetSingleton(name) getGDNativeAPI().globalGetSingleton(name)

View file

@ -66,6 +66,8 @@ type
GodotString* {.byref.} = object GodotString* {.byref.} = object
data: array[sizeof(int), byte] data: array[sizeof(int), byte]
cwchar_t* {.importc: "wchar_t", nodecl.} = object
GodotCharString* {.byref.} = object GodotCharString* {.byref.} = object
data: array[sizeof(int), byte] data: array[sizeof(int), byte]
@ -305,3 +307,6 @@ type
GodotClassConstructor* = proc (): ptr GodotObject {. GodotClassConstructor* = proc (): ptr GodotObject {.
noconv, gcsafe, locks: 0, raises: [], tags: [].} 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)))

View file

@ -3,9 +3,6 @@
import godotinternaltypes, gdnativeapi import godotinternaltypes, gdnativeapi
import core.godotcoretypes 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, template genPoolArrayAPI(ArrayT, initIdent, DataT,
newProc, newCopyProc, newWithArrayProc, appendProc, newProc, newCopyProc, newWithArrayProc, appendProc,
appendArrayProc, insertProc, invertProc, pushBackProc, appendArrayProc, insertProc, invertProc, pushBackProc,

View file

@ -22,6 +22,12 @@ proc `&`*(self, b: GodotString): GodotString {.inline.} =
proc deinit*(self: var GodotString) {.inline.} = proc deinit*(self: var GodotString) {.inline.} =
getGDNativeAPI().stringDestroy(self) 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 = proc `$`*(self: GodotString): string =
## Converts the ``GodotString`` into Nim string ## Converts the ``GodotString`` into Nim string
var charStr = getGDNativeAPI().stringUtf8(self) var charStr = getGDNativeAPI().stringUtf8(self)

View file

@ -508,7 +508,7 @@ proc genType(obj: ObjectDecl): NimNode {.compileTime.} =
# Register Godot object # Register Godot object
let parentName = if obj.parentName.isNil: newStrLitNode("Object") let parentName = if obj.parentName.isNil: newStrLitNode("Object")
else: newStrLitNode(obj.parentName) else: newStrLitNode(obj.parentName)
let classNameLit = newCStringLit(obj.name) let classNameLit = newStrLitNode(obj.name)
let classNameIdent = ident(obj.name) let classNameIdent = ident(obj.name)
let isRef: bool = if obj.parentName.isNil: false let isRef: bool = if obj.parentName.isNil: false
else: obj.parentName in refClasses else: obj.parentName in refClasses

View file

@ -1,6 +1,6 @@
# Copyright 2018 Xored Software, Inc. # Copyright 2018 Xored Software, Inc.
import tables, typetraits, macros import tables, typetraits, macros, unicode
import gdnativeapi import gdnativeapi
import core.godotcoretypes, core.godotbase import core.godotcoretypes, core.godotbase
import core.vector2, core.rect2, import core.vector2, core.rect2,
@ -89,21 +89,71 @@ type
## values. ## values.
## See documentation of ``GodotPropertyHint`` for description of formats. ## See documentation of ``GodotPropertyHint`` for description of formats.
FNV1Hash = uint32
proc isFinalized*(obj: NimGodotObject): bool {.inline.} = proc isFinalized*(obj: NimGodotObject): bool {.inline.} =
obj.isFinalized obj.isFinalized
var classRegistry {.threadvar.}: TableRef[cstring, ObjectInfo] var classRegistry {.threadvar.}: TableRef[FNV1Hash, ObjectInfo]
var classRegistryStatic* {.compileTime.}: TableRef[cstring, ObjectInfo] var classRegistryStatic* {.compileTime.}: TableRef[FNV1Hash, ObjectInfo]
## Compile-time variable used for implementation of several procedures ## Compile-time variable used for implementation of several procedures
## and macros ## and macros
static: static:
classRegistryStatic = newTable[cstring, ObjectInfo]() classRegistryStatic = newTable[FNV1Hash, ObjectInfo]()
static: static:
import sets, strutils import sets, strutils
var nativeClasses {.compileTime.} = newSeq[string]() var nativeClasses {.compileTime.} = newSeq[string]()
var refClasses* {.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:
let c = charsPtr.offset(i).lsb
appendFNV1Hash(result, c.uint8)
proc fnv1Hash(godotClassName: string): FNV1Hash {.compileTime.} =
var charsCount = godotClassName.len
if godotClassName.endsWith("SW"):
charsCount -= 2
initFNV1Hash(result)
var i = 0
for rune in runes(godotClassName):
if i >= 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 = proc getClassName*(o: NimGodotObject): string =
o.godotObject.getClassName() o.godotObject.getClassName()
@ -179,12 +229,12 @@ macro isResource(T: typedesc): bool =
result = if inherits(getType(T), "Resource"): ident("true") result = if inherits(getType(T), "Resource"): ident("true")
else: ident("false") else: ident("false")
template registerClass*(T: typedesc; godotClassName: cstring, template registerClass*(T: typedesc; godotClassName: string or cstring,
native: bool) = native: bool) =
## Registers the specified Godot type. ## Registers the specified Godot type.
## Used by ``gdobj`` macro and `godotapigen <godotapigen.html>`_. ## Used by ``gdobj`` macro and `godotapigen <godotapigen.html>`_.
if classRegistry.isNil: if classRegistry.isNil:
classRegistry = newTable[cstring, ObjectInfo]() classRegistry = newTable[FNV1Hash, ObjectInfo]()
let constructor = proc(): NimGodotObject = let constructor = proc(): NimGodotObject =
var t: T var t: T
new(t, nimGodotObjectFinalizer[T]) new(t, nimGodotObjectFinalizer[T])
@ -198,14 +248,22 @@ template registerClass*(T: typedesc; godotClassName: cstring,
isNative: native, isNative: native,
isRef: isRef isRef: isRef
) )
classRegistry[godotClassName] = objInfo classRegistry[fnv1Hash($godotClassName)] = objInfo
static: static:
let objInfoStatic = ObjectInfo( let objInfoStatic = ObjectInfo(
baseNativeClass: base, baseNativeClass: base,
isNative: native, isNative: native,
isRef: isRef, 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: when isRef:
static: static:
refClasses.add(T.name) refClasses.add(T.name)
@ -214,10 +272,10 @@ template registerClass*(T: typedesc; godotClassName: cstring,
nativeClasses.add(T.name) nativeClasses.add(T.name)
proc newNimGodotObject[T: NimGodotObject]( 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 classRegistry.isNil)
assert(not godotObject.isNil) assert(not godotObject.isNil)
let objInfo = classRegistry.getOrDefault(godotClassName) let objInfo = classRegistry.getOrDefault(fnv1Hash(godotClassName))
if objInfo.constructor.isNil: if objInfo.constructor.isNil:
printError("Nim constructor not found for class " & $godotClassName) printError("Nim constructor not found for class " & $godotClassName)
else: else:
@ -240,9 +298,11 @@ proc asNimGodotObject*[T: NimGodotObject](
# Could be data from other bindings # Could be data from other bindings
result = nil result = nil
if result.isNil: if result.isNil:
var classNameStr = godotObject.getClassNameRaw()
result = newNimGodotObject[T]( result = newNimGodotObject[T](
godotObject, cstring(godotObject.getClassName()), godotObject, classNameStr,
forceNativeObject or noRef) forceNativeObject or noRef)
deinit(classNameStr)
proc newVariant*(obj: NimGodotObject): Variant {.inline.} = proc newVariant*(obj: NimGodotObject): Variant {.inline.} =
newVariant(obj.godotObject) newVariant(obj.godotObject)
@ -363,17 +423,18 @@ proc newOwnObj[T: NimGodotObject](name: cstring): T =
proc gdnew*[T: NimGodotObject](): T = proc gdnew*[T: NimGodotObject](): T =
## Instantiates new object of type ``T``. ## Instantiates new object of type ``T``.
const godotName = asCString(toGodotName(T)) const godotName = toGodotName(T)
const objInfo = classRegistryStatic[godotName] const cGodotName = asCString(godotName)
const objInfo = classRegistryStatic[fnv1Hash(godotName)]
when objInfo.isNative: when objInfo.isNative:
let godotObject = getClassConstructor(godotName)() let godotObject = getClassConstructor(cGodotName)()
new(result, nimGodotObjectFinalizer[T]) new(result, nimGodotObjectFinalizer[T])
result.godotObject = godotObject result.godotObject = godotObject
when objInfo.isRef: when objInfo.isRef:
godotObject.initRef() godotObject.initRef()
result.isRef = true result.isRef = true
else: else:
result = newOwnObj[T](godotName) result = newOwnObj[T](cGodotName)
proc newCallError*(err: VariantCallError): ref CallError = proc newCallError*(err: VariantCallError): ref CallError =
## Instantiates ``CallError`` from Godot ``err``. ## Instantiates ``CallError`` from Godot ``err``.