Initial version

This commit is contained in:
Ruslan Mustakov 2017-07-11 22:22:17 +07:00
commit f1c7e80c51
30 changed files with 4508 additions and 0 deletions

472
godot/nim/godotmacros.nim Normal file
View file

@ -0,0 +1,472 @@
# Copyright 2017 Xored Software, Inc.
import macros, tables, typetraits
import "../core/godotcore.nim"
import godotnim
type
VarDecl = ref object
name: NimNode
typ: NimNode
defaultValue: NimNode
isNoGodot: bool
hint: string
hintTip: string
usage: string
MethodDecl = ref object
name: string
args: seq[VarDecl]
isVirtual: bool
returnType: NimNode
nimNode: NimNode
isNoGodot: bool
ObjectDecl = ref object
name: string
parentName: string
fields: seq[VarDecl]
methods: seq[MethodDecl]
ParseError = object of Exception
template parseError(node: NimNode, msg: string) =
raise newException(ParseError, lineinfo(node) & ": " & msg)
proc extractNames(definition: NimNode):
tuple[name, parentName: string] =
if definition.kind == nnkIdent:
result.name = $(definition.ident)
else:
if not (definition.kind == nnkInfix and definition[0].ident == !"of"):
parseError(definition, "invalid type definition")
result.name = $(definition[1].ident)
case definition[2].kind:
of nnkIdent:
result.parentName = $definition[2].ident
else:
parseError(definition[2], "parent type expected")
proc newRStrLit(s: string): NimNode {.compileTime.} =
result = newNimNode(nnkRStrLit)
result.strVal = s
proc newCStringLit(s: string): NimNode {.compileTime.} =
newNimNode(nnkCallStrLit).add(ident("cstring"), newRStrLit(s))
iterator pragmas(node: NimNode):
tuple[key: NimIdent, value: string, index: int] =
assert node.kind in {nnkPragma, nnkEmpty}
for index in countdown(node.len - 1, 0):
if node[index].kind == nnkExprColonExpr:
let val = $(node[index][1])
yield (node[index][0].ident, val, index)
elif node[index].kind == nnkIdent:
yield (node[index].ident, nil, index)
proc hasPragma(statement: NimNode, pname: string): bool =
## Checks if the pragma is present in the `statement`
if not (RoutineNodes.contains(statement.kind) or
statement.kind == nnkPragmaExpr):
return false
var pragmas = if RoutineNodes.contains(statement.kind): statement.pragma()
else: statement[1]
let pnameIdent = !pname
for ident, val, i in pragmas(pragmas):
if ident == pnameIdent:
return true
proc removePragma(statement: NimNode, pname: string): bool =
## Removes the pragma from the node and returns whether pragma was removed
if not (RoutineNodes.contains(statement.kind) or
statement.kind == nnkPragmaExpr):
return false
var pragmas = if RoutineNodes.contains(statement.kind): statement.pragma()
else: statement[1]
let pnameIdent = !pname
for ident, val, i in pragmas(pragmas):
if ident == pnameIdent:
pragmas.del(i)
return true
proc removeStrPragma(statement: NimNode,
pname: string): string {.compileTime.} =
## Removes the pragma from the node and returns value of the pragma
## Works for routine nodes or nnkPragmaExpr
if not (RoutineNodes.contains(statement.kind) or
statement.kind == nnkPragmaExpr):
return nil
result = nil
var pragmas = if RoutineNodes.contains(statement.kind): statement.pragma()
else: statement[1]
let pnameIdent = !pname
for ident, val, i in pragmas(pragmas):
if ident == pnameIdent:
pragmas.del(i)
return (if val.isNil: "" else: val)
proc isExported(node: NimNode): bool {.compileTime.} =
if node.kind == nnkPragmaExpr:
result = isExported(node[0])
elif node.kind == nnkPostfix:
result = ($node[0] == "*")
proc identDefsToVarDecls(identDefs: NimNode): seq[VarDecl] =
assert(identDefs.kind == nnkIdentDefs)
result = newSeqOfCap[VarDecl](identDefs.len - 2)
for i in 0..<(identDefs.len - 2):
let nameNode = identDefs[i].copyNimTree()
let hint = removeStrPragma(nameNode, "hint")
let hintTip = removeStrPragma(nameNode, "tip")
let usage = removeStrPragma(nameNode, "usage")
let isGdExport = removePragma(nameNode, "gdExport")
if not nameNode.isExported() and isGdExport:
parseError(nameNode, "gdExport is not applicable for private fields")
let isNoGodot = not nameNode.isExported() or not isGdExport
result.add(VarDecl(
name: if nameNode.kind == nnkPragmaExpr: nameNode[0].basename()
else: nameNode.basename(),
typ: identDefs[identDefs.len - 2],
defaultValue: identDefs[identDefs.len - 1],
hint: hint,
hintTip: hintTip,
isNoGodot: isNoGodot,
usage: usage
))
proc parseMethod(meth: NimNode): MethodDecl =
assert(meth.kind in {nnkProcDef, nnkMethodDef})
let methName = meth[0]
let isGdExport = removePragma(meth, "gdExport")
if not methName.isExported() and isGdExport:
parseError(meth, "gdExport is not applicable for private procs/methods")
let isNoGodot = meth.kind != nnkMethodDef and (
not methName.isExported() or
not isGdExport
) or removePragma(meth, "noExport")
result = MethodDecl(
name: $meth[0].basename,
args: newSeq[VarDecl](),
returnType: meth[3][0],
isVirtual: meth.kind == nnkMethodDef,
isNoGodot: isNoGodot,
nimNode: meth
)
for i in 1..<meth[3].len:
var identDefs = meth[3][i]
result.args.add(identDefsToVarDecls(identDefs))
proc parseVarSection(decl: NimNode): seq[VarDecl] =
assert(decl.kind == nnkVarSection)
result = identDefsToVarDecls(decl[0])
proc parseType(definition, callSite: NimNode): ObjectDecl =
let body = callSite[^1]
result = ObjectDecl(
fields: newSeq[VarDecl](),
methods: newSeq[MethodDecl]()
)
(result.name, result.parentName) = extractNames(definition)
if result.parentName.isNil: result.parentName = "Object"
for statement in body:
case statement.kind:
of nnkVarSection:
let varSection = parseVarSection(statement)
result.fields.add(varSection)
of nnkProcDef, nnkMethodDef:
let meth = parseMethod(statement)
result.methods.add(meth)
of nnkCommentStmt:
discard
else:
parseError(statement, "field or method declaration expected")
macro invokeVarArgs(procIdent, objIdent;
minArgs, maxArgs: static[int], numArgsIdent,
argSeqIdent; argTypes: seq[NimNode],
hasReturnValue: static[bool]): typed =
## Produces statement in form:
##
## .. code-block:: nim
## case numArgs:
## of 0:
## meth(self)
## of 1:
## let (arg0, err) = godotToNim[ArgT](argSeq[0])
## if err != ConversionResult.OK:
## error(...)
## return
## meth(self, arg0)
## else:
## error(...)
template conv(procLit, argT, argSeq, idx, argIdent, errIdent) =
let (argIdent, errIdent) = godotToNim[argT](argSeq[idx])
if errIdent != ConversionResult.OK:
let errorKind = if errIdent == ConversionResult.TypeError: "a type error"
else: "a range error"
printError(
"Failed to invoke Nim procedure " & $procLit &
": " & errorKind & " has occurred when converting argument " & $idx &
" of Godot type " & $argSeq[idx].getType())
return
result = newNimNode(nnkCaseStmt)
result.add(numArgsIdent)
for i in minArgs..maxArgs:
let branch = newNimNode(nnkOfBranch).add(newIntLitNode(i))
let branchBody = newStmtList()
let invocation = newNimNode(nnkCall)
invocation.add(procIdent)
invocation.add(objIdent) # self
for idx in 0..<i:
let argIdent = genSym(nskLet, "arg")
let errIdent = genSym(nskLet, "err")
let argT = argTypes[idx]
branchBody.add(getAst(conv($procIdent, argT,
argSeqIdent, idx, argIdent, errIdent)))
invocation.add(argIdent)
if hasReturnValue:
branchBody.add(newNimNode(nnkAsgn).add(ident("result"),
newCall("toGodot", invocation)))
else:
branchBody.add(invocation)
branchBody.add(newNimNode(nnkAsgn).add(ident("result"),
newCall("variant")))
branch.add(branchBody)
result.add(branch)
template printInvokeErr(procName, minArgs, maxArgs, numArgs) =
printError(
"Failed to invoke Nim procedure " & procName &
": expected " & $minArgs & "-" & $maxArgs &
" arguments, but got " & $numArgs)
result.add(newNimNode(nnkElse).add(getAst(
printInvokeErr($procIdent.ident, minArgs, maxArgs, numArgsIdent))))
proc typeError(nimType: cstring, value: string, godotType: VariantType,
className: cstring, propertyName: cstring): string =
result = "Tried to assign incompatible value " & value & " (" & $godotType &
") to field \"" & $propertyName & "\" (" & $nimType & ") of " &
$className
proc rangeError(nimType: cstring, value: string, className: cstring,
propertyName: cstring): string =
result = "Tried to assign the out-of-range value " & value &
" to field \"" & $propertyName & "\" (" & $nimType & ") of " &
$className
proc nimDestroyFunc(obj: ptr GodotObject, methData: pointer,
userData: pointer) {.noconv.} =
let nimObj = cast[ref NimGodotObject](userData)
nimObj.removeGodotObject()
GC_unref(nimObj)
template registerGodotClass(classNameIdent, classNameLit,
baseNameLit, createFuncIdent) =
proc createFuncIdent(obj: ptr GodotObject,
methData: pointer): pointer {.noconv.} =
let nimObj = new(classNameIdent)
setGodotObject(nimObj, obj)
GC_ref(nimObj)
result = cast[pointer](nimObj)
let createFuncObj = GodotInstanceCreateFunc(
createFunc: createFuncIdent
)
let destroyFuncObj = GodotInstanceDestroyFunc(
destroyFunc: nimDestroyFunc
)
registerClass(classNameIdent, classNameLit, false)
godotScriptRegisterClass(classNameLit, baseNameLit,
createFuncObj, destroyFuncObj)
template registerGodotField(classNameLit, classNameIdent, propNameLit,
propNameIdent, propTypeLit, propTypeIdent,
setFuncIdent, getFuncIdent, hintTipLit,
hintIdent, usageIdent, hasDefaultValue,
defaultValueNode) =
proc setFuncIdent(obj: ptr GodotObject, methData: pointer,
nimPtr: pointer, val: Variant) {.noconv.} =
let (nimVal, err) = godotToNim[propTypeIdent](val)
case err:
of ConversionResult.OK:
cast[classNameIdent](nimPtr).propNameIdent = nimVal
of ConversionResult.TypeError:
let errStr = typeError(propTypeLit, $val, val.getType(),
classNameLit, propNameLit)
printError(errStr)
of ConversionResult.RangeError:
let errStr = rangeError(propTypeLit, $val,
classNameLit, propNameLit)
printError(errStr)
proc getFuncIdent(obj: ptr GodotObject, methData: pointer,
nimPtr: pointer): Variant {.noconv.} =
result = nimToGodot(cast[classNameIdent](nimPtr).propNameIdent)
let setFunc = GodotPropertySetFunc(
setFunc: setFuncIdent
)
let getFunc = GodotPropertyGetFunc(
getFunc: getFuncIdent
)
var attr = GodotPropertyAttributes(
typ: ord(godotVariantType(propTypeIdent)),
hintString: hintTipLit.toGodotString(),
hint: GodotPropertyHint.hintIdent,
usage: GodotPropertyUsageFlags.usageIdent
)
when hasDefaultValue:
attr.defaultValue = (defaultValueNode).toGodot()
godotScriptRegisterProperty(classNameLit, propNameLit,
attr, setFunc, getFunc)
static:
import strutils
proc toGodotStyle(s: string): string {.compileTime.} =
result = newStringOfCap(s.len + 10)
for c in s:
if c.isUpperAscii():
result.add('_')
result.add(c.toLowerAscii())
else:
result.add(c)
proc genType(obj: ObjectDecl): NimNode {.compileTime.} =
result = newNimNode(nnkStmtList)
# 1. Nim type definition
let typeDef = newNimNode(nnkTypeDef)
result.add(newNimNode(nnkTypeSection).add(typeDef))
typeDef.add(postfix(ident(obj.name), "*"))
typeDef.add(newEmptyNode())
let objTy = newNimNode(nnkObjectTy)
typeDef.add(newNimNode(nnkRefTy).add(objTy))
objTy.add(newEmptyNode())
if obj.parentName.isNil:
objTy.add(newEmptyNode())
else:
objTy.add(newNimNode(nnkOfInherit).add(ident(obj.parentName)))
let recList = newNimNode(nnkRecList)
objTy.add(recList)
for decl in obj.fields:
if not decl.defaultValue.isNil and decl.defaultValue.kind != nnkEmpty:
parseError(decl.defaultValue,
"Default values are not supported for fields for now.")
recList.add(newIdentDefs(decl.name, decl.typ))
# 2. {.this: self.} for convenience
result.add(newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
ident("this"), ident("self")
)))
# 3. Nim proc defintions
var decls = newSeqOfCap[NimNode](obj.methods.len)
for meth in obj.methods:
let selfArg = newIdentDefs(ident("self"), ident(obj.name))
meth.nimNode.params.insert(1, selfArg)
let decl = meth.nimNode.copyNimTree()
decl.body = newEmptyNode()
decl.addPragma(ident("gcsafe"))
decl.addPragma(newNimNode(nnkExprColonExpr).add(ident("locks"),
newIntLitNode(0)))
decls.add(decl)
# add forward declarations first
for decl in decls:
result.add(decl)
for meth in obj.methods:
result.add(meth.nimNode)
# 4. Register Godot object
let parentName = if obj.parentName.isNil: newNilLit()
else: newStrLitNode(obj.parentName)
let classNameLit = newCStringLit(obj.name)
let classNameIdent = ident(obj.name)
result.add(getAst(
registerGodotClass(classNameIdent, classNameLit, parentName,
genSym(nskProc, "createFunc"))))
# 5. Register fields (properties)
for field in obj.fields:
if field.isNoGodot: continue
let hintTip = if field.hintTip.isNil: ""
else: field.hintTip
let hint = if field.hint.isNil: "None"
else: field.hint
let usage = if field.usage.isNil: "Default"
else: field.usage
let hasDefaultValue = not field.defaultValue.isNil and
field.defaultValue.kind != nnkEmpty
let hintIdent = ident(hint)
let usageIdent = ident(usage)
result.add(getAst(
registerGodotField(classNameLit, classNameIdent,
newCStringLit($field.name.basename()),
ident(field.name), newCStringLit($field.typ),
field.typ, genSym(nskProc, "setFunc"),
genSym(nskProc, "getFunc"),
newStrLitNode(hintTip), hintIdent, usageIdent,
ident($hasDefaultValue), field.defaultValue)))
# 6. Register methods
template registerGodotMethod(classNameLit, classNameIdent, methodNameIdent,
methodNameLit, minArgs, maxArgs,
argTypes, methFuncIdent, hasReturnValue) =
proc methFuncIdent(obj: ptr GodotObject, methodData: pointer,
userData: pointer, numArgs: cint,
args: var ptr array[MAX_ARG_COUNT, Variant]):
Variant {.noconv.} =
let self = cast[classNameIdent](userData)
when defined(release):
invokeVarArgs(methodNameIdent, self, minArgs, maxArgs, numArgs,
args, argTypes, hasReturnValue)
else:
try:
invokeVarArgs(methodNameIdent, self, minArgs, maxArgs, numArgs,
args, argTypes, hasReturnValue)
except:
let ex = getCurrentException()
printError("Unhandled Nim exception (" &
$ex.name & "): " &
ex.msg & "\n" & ex.getStackTrace())
let meth = GodotInstanceMethod(
meth: methFuncIdent
)
godotScriptRegisterMethod(classNameLit, methodNameLit,
GodotMethodAttributes(), meth)
for meth in obj.methods:
if meth.isNoGodot: continue
let maxArgs = meth.args.len
var minArgs = maxArgs
var argTypes = newSeq[NimNode]()
for idx, arg in meth.args:
if minArgs == maxArgs and not arg.defaultValue.isNil and
arg.defaultValue.kind != nnkEmpty:
minArgs = idx
argTypes.add(arg.typ)
let godotMethodName = if meth.isVirtual: "_" & toGodotStyle(meth.name)
else: toGodotStyle(meth.name)
let hasReturnValueBool = not (meth.returnType.isNil or
meth.returnType.kind == nnkEmpty or
$meth.returnType == "void")
let hasReturnValue = if hasReturnValueBool: ident("true")
else: ident("false")
result.add(getAst(
registerGodotMethod(classNameLit, classNameIdent, ident(meth.name),
newCStringLit(godotMethodName), minArgs, maxArgs,
argTypes, genSym(nskProc, "methFunc"),
hasReturnValue)))
macro gdobj*(definition: untyped, body: untyped): typed =
let typeDef = parseType(definition, callsite())
result = genType(typeDef)

515
godot/nim/godotnim.nim Normal file
View file

@ -0,0 +1,515 @@
# Copyright 2017 Xored Software, Inc.
import tables, typetraits, macros
import "../core/godotbase.nim", "../core/godotcore.nim"
import "../core/strings.nim", "../core/vector2.nim", "../core/rect2.nim",
"../core/vector3.nim", "../core/transform2d.nim",
"../core/plane.nim", "../core/quat.nim", "../core/rect3.nim",
"../core/basis.nim", "../core/transform.nim", "../core/color.nim",
"../core/nodepath.nim", "../core/rid.nim", "../core/dictionary.nim",
"../core/arrays.nim", "../core/poolarray.nim", "../core/variant.nim"
## Definition of NimGodotObject and converters for built-in types.
type
NimGodotObject* = ref object of RootObj
godotObject: ptr GodotObject
ConversionResult* {.pure.} = enum
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 Rect3 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
err*: VariantCallError
ObjectInfo = object
constructor: proc(): NimGodotObject {.gcsafe, nimcall.}
baseNativeClass: cstring
isNative: bool
var classRegistry {.threadvar.}: TableRef[cstring, ObjectInfo]
static:
import sets, strutils
var nativeClasses {.compileTime.} = initSet[string]()
proc godotFinalizer[T: NimGodotObject](obj: T) =
# hmm, not sure yet how to deal with references correctly
# when T is Reference:
# obj.godotObject.deinit()
discard
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.isNil:
result = newNilLit()
else:
let rStr = newNimNode(nnkRStrLit)
rStr.strVal = baseT
result = newNimNode(nnkCallStrLit).add(ident("cstring"), rStr)
template registerClass*(T: typedesc; godotClassName: cstring,
isNativeParam: bool) =
if classRegistry.isNil:
classRegistry = newTable[cstring, ObjectInfo]()
let constructor = proc(): NimGodotObject =
var t: T
new(t, godotFinalizer[T])
result = t
const base = baseNativeType(T)
classRegistry[godotClassName] = ObjectInfo(
constructor: constructor,
baseNativeClass: base,
isNative: isNativeParam
)
when isNativeParam:
static:
nativeClasses.incl(T.name)
proc newNimGodotObject[T: NimGodotObject](
godotObject: ptr GodotObject, godotClassName: cstring): T =
assert(not classRegistry.isNil)
assert(not godotObject.isNil)
let objInfo = classRegistry.getOrDefault(godotClassName)
if objInfo.constructor.isNil:
printError("Nim constructor not found for class " & $godotClassName)
else:
result = T(objInfo.constructor())
result.godotObject = godotObject
proc asNimGodotObject*[T: NimGodotObject](godotObject: ptr GodotObject): T =
if godotObject.isNil: return nil
let userDataPtr = godotObject.getUserData()
if not userDataPtr.isNil:
result = cast[T](userDataPtr)
if result.godotObject != godotObject:
# Could be data from other bindings
result = nil
if result.isNil:
result = newNimGodotObject[T](
godotObject, cstring(godotObject.getClassName()))
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``)
if obj.isNil or not (obj of T):
when not defined(release):
if not obj.isNil:
printError("Failed to cast object to " &
t.name & "\n" & getStackTrace())
result = nil
else:
result = T(obj)
proc getSingletonGodot*(name: cstring): ptr GodotObject {.
importc: "godot_global_get_singleton", header: "../godot.h".}
proc newRStrLit(s: string): NimNode {.compileTime.} =
result = newNimNode(nnkRStrLit)
result.strVal = s
static:
import strutils
macro toGodotName(T: typedesc): cstring =
let nameStr: string = (($T.getType()[1][1].symbol).split(':')[0])
let godotName = case nameStr:
of "File", "Directory", "Thread", "Mutex", "Semaphore":
"_" & nameStr
else:
nameStr
result = newNimNode(nnkCallStrLit).add(
ident("cstring"), newRStrLit(godotName))
proc getSingleton*[T: NimGodotObject](): T =
const godotName = toGodotName(T)
let singleton = getSingletonGodot(godotName)
if singleton.isNil:
printError("Tried to get non-existing singleton of type " & $godotName)
else:
result = asNimGodotObject[T](singleton)
proc gdnew*[T: NimGodotObject](): T =
const godotName = toGodotName(T)
let obj = godotNew(godotName)
if obj.isNil:
printError("godot_new returned nil for type " & $godotName)
result = asNimGodotObject[T](obj)
# const godotName = toGodotName(T)
# let objInfo = classRegistry.getOrDefault(godotName)
# if objInfo.constructor.isNil:
# printError("gdnew invoked on unregistered class: " & $godotName)
# return
# let godotConstructor = getClassConstructor(
# if objInfo.isNative: godotName else: objInfo.baseNativeClass)
# printError("Base native class for " & T.name & ": " & $objInfo.baseNativeClass)
# when not defined(release):
# if godotConstructor.isNil:
# printError("Failed to obtain Godot constrcutor for type: " & $godotName)
# return
# let godotObject = godotConstructor()
# when not defined(release):
# if godotObject.isNil:
# printError("Godot constructor returned nil - must never happen")
# return
# new(result, godotFinalizer[T])
# result.setGodotObject(godotObject)
proc newCallError*(err: VariantCallError): ref CallError =
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 =
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 removeGodotObject*(nimObj: NimGodotObject) =
## Used from destructor
nimObj.godotObject = nil
proc setGodotObject*(nimObj: NimGodotObject, obj: ptr GodotObject) {.inline.} =
assert(not obj.isNil)
assert(nimObj.godotObject.isNil) # reassignment is not allowed
nimObj.godotObject = obj
proc godotObject*(nimObj: NimGodotObject): ptr GodotObject {.inline.} =
nimObj.godotObject
proc godotVariantType*(T: typedesc[NimGodotObject]): VariantType {.inline.} =
VariantType.Object
proc toGodot*(self: NimGodotObject): Variant {.inline.} =
variant(self.godotObject)
proc fromGodot*[T: NimGodotObject](self: var T,
val: Variant): ConversionResult =
if val.getType() == VariantType.Object:
let objPtr = val.asObject()
self = asNimGodotObject[T](objPtr)
if self.isNil:
result = ConversionResult.TypeError
elif val.getType() == VariantType.Nil:
self = nil
else:
result = ConversionResult.TypeError
proc godotVariantType*(T: typedesc[enum]): VariantType {.inline.} =
VariantType.Int
proc toGodot*[T: enum](self: T): Variant {.inline.} =
variant(ord(self))
proc fromGodot*[T: enum](self: var T, val: Variant): ConversionResult =
if val.getType() == VariantType.Int:
self = T(val.asInt())
else:
result = ConversionResult.TypeError
proc toGodot*(self: Variant): Variant {.inline.} =
result = self
proc fromGodot*(self: var Variant, val: Variant): ConversionResult {.inline.} =
self = val
proc godotVariantType*(T: typedesc[SomeGodotOrNum]): VariantType {.inline.} =
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 Rect3:
VariantType.Rect3
elif T is Basis:
VariantType.Basis
elif T is Transform:
VariantType.Transform
elif T is Color:
VariantType.Color
elif T is NodePath:
VariantType.NodePath
elif T is RID:
VariantType.RID
elif T is GodotObject:
VariantType.Object
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 toGodot*[T: SomeGodotOrNum](val: T): Variant {.inline.} =
result = variant(val)
proc fromGodot*[T: SomeSignedInt or SomeUnsignedInt](
self: var T, val: Variant): ConversionResult =
if val.getType() != VariantType.Int:
result = ConversionResult.TypeError
else:
var intVal: (when T is SomeSignedInt: int64
else: uint64)
intVal = when T is SomeSignedInt: val.asInt()
else: val.asUint()
if intVal > high(T) or intVal < low(T):
result = ConversionResult.RangeError
else:
self = T(intVal)
proc fromGodot*[T: SomeFloat](self: var T, val: Variant): ConversionResult =
if val.getType() == VariantType.Real:
self = val.asReal()
elif val.getType() == VariantType.Int:
self = T(val.asInt())
else:
result = ConversionResult.TypeError
proc fromGodot*[T: SomeGodot](self: var T, val: Variant): ConversionResult =
if godotVariantType(T) != 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 Rect3:
self = val.asRect3()
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 godotVariantType*(T: typedesc[string]): VariantType {.inline.} =
VariantType.String
proc toGodot*(s: string): Variant {.inline.} =
variant(s.toGodotString())
proc fromGodot*(s: var string, val: Variant): ConversionResult =
if val.getType() != VariantType.String:
result = ConversionResult.TypeError
else:
s = $val.asGodotString()
proc godotVariantType*(T: typedesc[seq]): VariantType =
VariantType.Array
proc godotVariantType*(T: typedesc[array]): VariantType =
VariantType.Array
proc toGodot*[T](s: openarray[T]): Variant =
var arr: Array
initArray(arr)
mixin toGodot
for item in s:
arr.add(toGodot(item))
result = variant(arr)
proc fromGodot*[T](s: var seq[T], val: Variant): ConversionResult =
if val.getType() != VariantType.Array:
result = ConversionResult.TypeError
else:
let arr = val.asArray()
s = newSeq[T](arr.len)
for idx, item in arr:
mixin fromGodot
let convResult = fromGodot(s[idx], item)
if convResult != ConversionResult.OK:
s = nil
return convResult
proc fromGodot*[T: array](s: var T, val: Variant): ConversionResult =
if val.getType() != VariantType.Array:
result = ConversionResult.TypeError
else:
let arr = val.asArray()
if s.len != arr.len:
return ConversionResult.TypeError
for idx, item in arr:
mixin fromGodot
let convResult = fromGodot(s[idx], item)
if convResult != ConversionResult.OK:
return convResult
proc godotVariantType*(T: typedesc[Table|TableRef]): VariantType {.inline.} =
VariantType.Dictionary
proc toGodot*[T: Table or TableRef](t: T): Variant =
var dict: Dictionary
initDictionary(dict)
mixin toGodot
for k, v in t.pairs():
dict[toGodot(k)] = toGodot(v)
result = variant(dict)
proc fromGodot*[T: Table or TableRef](t: var T, val: Variant): ConversionResult =
if val.getType() != VariantType.Dictionary:
result = ConversionResult.TypeError
else:
let dict = val.asDictionary()
mixin fromGodot
when t is Table:
t = initTable[type(t.keys()), type(t.values())]()
else:
t = newTable[type(t.keys()), type(t.values())]()
for k, v in dict:
var nimKey: type(t.keys())
var nimVal: type(t.values())
let keyResult = fromGodot(nimKey, k)
if keyResult != ConversionResult.OK:
when t is ref:
t = nil
return keyResult
let valResult = fromGodot(nimVal, v)
if valResult != ConversionResult.OK:
when t is ref:
t = nil
return valResult
t[nimKey] = nimVal
proc godotToNim*[T](val: Variant): (T, ConversionResult) =
mixin fromGodot
result[1] = fromGodot(result[0], val)
proc nimToGodot*[T](val: T): Variant =
mixin toGodot
when compiles(toGodot(val)):
result = toGodot(val)
else:
printError("Failed to convert Nim value of type " & T.name &
" into Variant")
initNilVariant(result)
{.emit: """/*TYPESECTION*/
void NimMain(void);
N_NOINLINE(void, setStackBottom)(void* thestackbottom);
""".}
const stackIncreases = defined(emscripten)
proc godot_native_init() {.cdecl, exportc, dynlib.} =
let stackBottom = godotStackBottom()
{.emit: """
NimMain();
setStackBottom(`stackBottom`);
""".}
GC_fullCollect()
GC_disable()
proc godot_native_terminate() {.cdecl, exportc, dynlib.} =
discard