Initial version
This commit is contained in:
commit
f1c7e80c51
30 changed files with 4508 additions and 0 deletions
472
godot/nim/godotmacros.nim
Normal file
472
godot/nim/godotmacros.nim
Normal 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
515
godot/nim/godotnim.nim
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue