diff --git a/.gitignore b/.gitignore index e45da94..e06f7ed 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,10 @@ nimcache/ /godot/godotapigen /godot/godotapigen.exe +/docgen +/docs/docbuild +/docs/docbuild.exe +/docs/docpublish +/docs/docpublish.exe + .DS_Store \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0d0ec4d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,54 @@ +sudo: false +language: c +os: + - osx +compiler: clang +env: + global: + - secure: o+IZkRS6J7bGYkiilm10KDeCTDIVHszZ9dPvb93cdUKX/9hFVRiUjDOvNXqBdyOv43JIAsRTppym4B2pfcp9cwLoVBJNW70GKhp6ChLZm3wFdabScWKemuG2YtAUyGiSmxbVmUZ4baltnADyGFHuYWgHLjx6AKO26SqrXRLJ0I1IzGNKKNIfoyZzm7qdFDGJRnSC7L+y1pOITK7dpy60D1vat4FhOLaz2K7L8T8tZYZFK7yyxKduFUlY3cy4z3TpjxMa9/ksCiPiuPDN5xOrVIw0AtgZ2gYodZJUEnfaRzFkgcZ4OJuTAMNMalu52mVAVAoDt4yc7BnbtfsYJzfIjTeLjjWKWz8mqqp/HpxcfVnmPKxV5zeN8UhbosT8vNjwretWbQcFndzt0ohK7E8uMsnDGER7sGPEhCZCttkId96gi7Ro1awPIKCqLaMqB43Bwo48nnW0oQgrfWe1ZKtREXWI4F1F2GMbU8Jsvv0N5GnPGd4RDieBH5KIkJXcttAwRk8AUQ4U5PVkQ/OK9xm9BH2tB0vrjZ4FZWnFPapKVcPUYw9deyyXOXwSMmDpuwkJIjqi3tRyBWJG9+tUlAJw8zyFkCPeN9kzyVH/PhbacwBMdSxew24VLAWWCUhQHCP+fcjpKio8uDUFQN3javIRV4N3mHJ9v0VAF7Ixzh8AviQ= + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty-3.9 + packages: + - build-essential + - scons + - pkg-config + - libx11-dev + - libxcursor-dev + - libasound2-dev + - libfreetype6-dev + - libgl1-mesa-dev + - libglu1-mesa-dev + - libssl-dev + - libxinerama-dev + - libxrandr-dev + +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install scons ; fi + +install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export GODOT_PLATFORM=osx; else export GODOT_PLATFORM=x11; fi + - git clone --depth=1 https://github.com/nim-lang/Nim.git + - cd Nim + - git checkout devel + - sh ci/build.sh + - export PATH=$PATH:"`pwd`/bin" + - ./koch tools -d:release + - nimble install -y + - cd .. + - git clone --depth=1 https://github.com/godotengine/godot.git godotengine + - cd godotengine + - scons -j 3 platform=$GODOT_PLATFORM + - export GODOT_BIN="`pwd`/bin/godot.$GODOT_PLATFORM.tools.64" + - cd .. + +script: + - set -e + - mkdir godotapi + - nim c -d:release -r godot/godotapigen.nim "$GODOT_BIN" godotapi + - nimble install -y + - nim c -c godotapi/godotall.nim + - nim c -d:release -r docs/docpublish.nim diff --git a/docs/docbuild.nim b/docs/docbuild.nim new file mode 100644 index 0000000..b48368d --- /dev/null +++ b/docs/docbuild.nim @@ -0,0 +1,159 @@ +# Copyright 2017 Xored Software, Inc. + +import threadpool, os, osproc, strutils, sequtils, pegs + +import "../godot/godotapigen.nim" + +const dirs = ["godot"/"core", "godot"/"internal", "godot"/"nim"] +const files = ["godot"/"godotapigen.nim", "godot"/"godotinternal.nim", + "godot"/"godot.nim"] +const indexFile = "docs"/"index.rst" +const gitHubUrl = "https://github.com/pragmagic/godot-nim" + +proc outName(outDir, file: string): string = + outDir / file.extractFilename().changeFileExt("html") + +template quoted(s: string): string = + ('"' & s & '"') + +proc execOrFail(cmd: string) = + echo "[exec] " & cmd + let ret = execShellCmd(cmd) + if ret != 0: + raise newException( + Exception, "Command quit with exit code " & $ret & ": " & cmd) + +template withDir(dir: string, body: typed) = + let curDir = getCurrentDir() + setCurrentDir(dir) + try: + body + finally: + setCurrentDir(curDir) + +iterator walkDirRec(dir: string, filter: set[PathComponent], + extensions: openarray[string]): string = + for file in walkDirRec(dir, filter): + if not extensions.anyIt(file.endsWith(it)): continue + yield file + +proc genApiFiles(targetDir, godotBin: string) = + let jsonFile = targetDir / "api.json" + try: + execOrFail(quoted(godotBin) & + " --gdnative-generate-json-api " & quoted(jsonFile)) + except: + # this fails unstably even if api.json is created successfully + discard + if not fileExists(jsonFile): + raise newException(Exception, "Failed to generate Godot API wrappers") + + genApi(targetDir, jsonFile) + + writeFile(targetDir / "nim.cfg", "path=\"$projectdir/../../godot\"") + +proc extractVersion(nimbleFile: string): string = + let contents = readFile(nimbleFile) + var matches: array[1, string] + doAssert match(contents, peg"""'version' \s* '=' \s* '"' {@} '"' """, matches) + result = matches[0] + +proc fixupHrefs(file: string) = + var contents = readFile(file) + contents = contents.replacef( + peg"""'href="' ('internal'/'core'/'nim') '/' {@} '"' """, + "href=\"$#\"") + if file.contains("godotapi"): + contents = contents.replacef( + peg"""'href="' {('godotinternal' / 'godot') '.html"'} """, + "href=\"../$#") + writeFile(file, contents) + +proc getGitHash(): string = + result = execProcess("git rev-parse HEAD") + if not result.isNil and result.len > 0: + result = result.replace("\L", "").replace("\r", "") + +proc buildDocs*(outDir, godotNimDir, godotBin: string) = + removeDir(outDir) + createDir(outDir) + let outDirAbs = expandFilename(outDir) + let godotBinAbs = expandFilename(godotBin) + + setCurrentDir(godotNimDir) + + let godotNimVersion = extractVersion("godot.nimble") + putEnv("godotnimversion", godotNimVersion) + putEnv("godotnimgithub", gitHubUrl) + let gitCommit = getGitHash() + + var allNimFiles = newSeq[string]() + allNimFiles.add(files) + for dir in dirs: + for file in walkDirRec(dir, {pcFile, pcDir}, [".nim"]): + allNimFiles.add(file) + for file in allNimFiles: + spawn execOrFail( + "nim doc -o:" & quoted(outName(outDirAbs, file)) & + " --git.url:" & quoted(gitHubUrl) & + " --git.commit:" & quoted(gitCommit) & + ' ' & quoted(file)) + + let godotApiFolder = outDirAbs / "godotapi" + createDir(godotApiFolder) + genApiFiles(godotApiFolder, godotBinAbs) + for file in walkDirRec(godotApiFolder, {pcFile, pcDir}, [".nim"]): + spawn execOrFail("nim doc " & quoted(file)) + + sync() + + var gitHash: string + withDir(godotBinAbs.parentDir()): + gitHash = getGitHash() + if gitHash.isNil or gitHash.len == 0: + raise newException(Exception, + "Godot executable is not under Git repository") + + var indexContent = readFile(indexFile) + var apiList = newStringOfCap(4096) + for file in walkDirRec(godotApiFolder, {pcFile, pcDir}): + if not file.endsWith(".html"): + removeFile(file) + else: + let moduleHtml = file.extractFileName() + let moduleName = moduleHtml.changeFileExt("") + apiList.add("* `" & moduleName & " `_\L") + indexContent = indexContent.replace("$GODOTAPI_CHANGESET_HASH", gitHash). + replace("$AUTO_GENERATED_GODOTAPI_LIST", apiList). + replace("$GODOTNIM_GITHUB_URL", gitHubUrl) + + let tmpRst = outDirAbs/"index.rst" + writeFile(tmpRst, indexContent) + try: + execOrFail("nim rst2html -o:" & quoted(outName(outDirAbs, tmpRst)) & + ' ' & quoted(tmpRst)) + finally: + removeFile(tmpRst) + + for file in walkDirRec(outDir, {pcFile, pcDir}, [".html"]): + spawn fixupHrefs(file) + + sync() + +when isMainModule: + const outDir = "docgen" + + if not fileExists("godot.nimble"): + echo "Must be executed from godot-nim root dir" + quit(-1) + + let godotBin = getEnv("GODOT_BIN") + if godotBin.len == 0: + echo "GODOT_BIN environment variable must point to Godot executable" + quit(-1) + + try: + buildDocs(outDir, getCurrentDir(), godotBin) + except: + echo getCurrentExceptionMsg() + quit(-1) diff --git a/docs/docpublish.nim b/docs/docpublish.nim new file mode 100644 index 0000000..02d56bb --- /dev/null +++ b/docs/docpublish.nim @@ -0,0 +1,94 @@ +# Copyright 2017 Xored Software, Inc. + +import os, pegs, strutils +import docbuild + +const indexTemplate = """ +godot-nim docs index + +

Documentation of Nim bindings for Godot Engine (GitHub)


+$VERSION_LIST + + +""" + +proc execOrQuit(cmd: string) = + let ret = execShellCmd(cmd) + if ret != 0: + quit(ret) + +proc walkDirRecRelative(dir: string, cb: proc (file: string), start = "") = + for kind, path in walkDir(dir, relative = true): + if kind in {pcFile, pcLinkToFile}: + cb(if start.len > 0: start / path else: path) + elif kind in {pcDir, pcLinkToDir}: + let fullPath = if start.len > 0: start / path else: path + walkDirRecRelative(dir / fullPath, cb, fullPath) + +proc publish(docDir, gitHubToken, repoSlug, branch, tag, changeset: string) = + let release = if branch == "master": branch else: tag + let commitComment = if release == "master": + "Update master documentation for changeset " & changeset + else: + "Update documentation for " & tag + + const repoDir = "gh-pages" + execOrQuit( + "git clone --depth=1 --branch=gh-pages https://github.com/$#.git $#" % + [repoSlug, repoDir]) + try: + removeDir(repoDir/release) + createDir(repoDir/release) + var versionList = newStringOfCap(4096) + for kind, path in walkDir(repoDir, relative = true): + if kind == pcDir and not path.startsWith("."): + versionList.add("$1
" % path) + let index = indexTemplate.replace("$REPO_SLUG", repoSlug). + replace("$VERSION_LIST", versionList) + writeFile(repoDir/"index.html", index) + + walkDirRecRelative(docDir) do (file: string): + createDir(parentDir(repoDir/release/file)) + copyFile(docDir/file, repoDir/release/file) + + setCurrentDir(repoDir) + try: + execOrQuit("git add --all") + execOrQuit("git commit -m \"$#\"" % commitComment) + execOrQuit("git push -fq \"https://$#@github.com/$#.git\" gh-pages" % + [gitHubToken, repoSlug]) + finally: + setCurrentDir("..") + finally: + removeDir(repoDir) + +when isMainModule: + proc getEnvOrQuit(key: string): string = + result = getEnv(key) + if result.len == 0: + echo "Expected environment variable: " & key + quit(1) + + if getEnv("TRAVIS_PULL_REQUEST").len > 0: + quit(0) + + let repoSlug = getEnvOrQuit("TRAVIS_REPO_SLUG") + let branch = getEnvOrQuit("TRAVIS_BRANCH") + let changeset = getEnvOrQuit("TRAVIS_COMMIT") + let tag = getEnv("TRAVIS_TAG") + + # let repoSlug = "pragmagic/godot-nim" + # let branch = "master" + # let changeset = "0fd0101432c1fed1004f50b035fcf74f75f004a8" + # let tag = "" + + let gitHubToken = getEnvOrQuit("GITHUB_TOKEN") + + if branch != "master" and not (tag =~ peg"^ 'v' \d+ '.' \d+ '.' \d+ $"): + quit(0) + + let godotBin = getEnvOrQuit("GODOT_BIN") + + const docDir = "docgen" + buildDocs(docDir, getCurrentDir(), godotBin) + publish(docDir, gitHubToken, repoSlug, branch, tag, changeset) diff --git a/docs/godotapi.rst b/docs/godotapi.rst new file mode 100644 index 0000000..9bea41d --- /dev/null +++ b/docs/godotapi.rst @@ -0,0 +1,12 @@ +=============== +Godot API (Nim) +=============== + +:Godot Git Hash: |godothash| + +.. contents:: + +This is an auto-generated index of Godot API. It's built from Git changeset +$GODOTAPI_CHANGESET_HASH. + +$AUTO_GENERATED_GODOTAPI_LIST \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..d127ea3 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,198 @@ +============================= +Nim bindings for Godot Engine +============================= + +:Author: Ruslan Mustakov +:Version: |godotnimversion| +:GitHub: `$GODOTNIM_GITHUB_URL <$GODOTNIM_GITHUB_URL>`_ + +.. contents:: + +``godot-nim`` library allows to create games on +`Godot Engine `_ with +`Nim programming language `_. Nim is a statically typed +language with an elegant Python-like syntax that compiles to native code. +It is garbage-collected, but its GC supports real-time mode which this library +makes use of. It means the GC will never run during game frames and will use +fixed amount of frame idle time to collect garbage. This leads to no stalls +and close to zero compromise on performance comparing to native languages with +manual memory management. + +If you are not familiar with Nim yet, it is recommended to go through the +`official tutorial `_. + +`VSCode `_ is the recommended editor for +working with Nim code. It is cross-platform and has the excellent +`nim plugin `_ +that supports most of the features you would expect from an IDE. +It also has `godot-tools plugin `_ +which adds features for editing GDScript and Godot resource files. + + +Getting Started +=============== + +Building Godot +-------------- + +The library requires a not yet released Godot version 3.0, which you can +build yourself by running the commands below (requires +`Git `_, +`Python 2.7 `_, +`SCons `_): + +.. code-block:: bash + git clone https://github.com/godotengine/godot.git + cd godot + scons platform= + +where ```` can be ``windows``, ``osx``, ``x11``. After build +is finished, Godot binaries will be under the ``bin`` folder. More details +about compiling Godot can be found in `Godot documentation +`_. + + +Building Nim +------------- + +The library requires a not yet released Nim version 0.17.1, which you can +build yourself by following instructions in the +`Nim repository `_. Make sure to also run +``./koch tools -d:release`` after the steps described there to build ``nimble`` +(package manager) and ``nimsuggest`` (IDE helper tool, +used by VSCode Nim plugin) + + +Creating Project +---------------- + +The fastest way to set up a Godot-Nim project is to use an existing stub: + +.. code-block:: bash + git clone --depth=1 https://github.com/pragmagic/godot-nim-stub.git myproject + +(you can then delete the .git directory within to untie the project from the +stub repository) + +The stub contains the necessary build configuration to compile your code for +desktop and mobile platforms, as well as a couple of very simple scenes to +help you get started. Consult the stub's `README +`_ for information about +compiling the project. + + +Adding Nim to Existing Project +------------------------------ + +If you would like to use Nim in an existing project: + +1. Copy ``nakefile.nim`` file and ``src`` directory from the stub described + in the previous section above your Godot project folder. Adjust paths in + build scripts (``nakefile.nim``, ``src/stub.nimble``) according to your + own project structure. + +2. Copy ``project/nimlib.tres`` to your Godot project folder. It is a + GDNative library resource that contains paths to dynamic libraries + compiled by Nim. + +3. Add ``NimRuntime`` as an `AutoLoad singleton `_ + to your project. To do this, copy ``project/scripts/NimRuntime.gdns`` into + your project and add these lines to ``project.godot``, adjusting path to + ``NimRuntime.gdns`` as necessary: +.. code-block:: cfg + [autoload] + NimRuntime="*res://scripts/NimRuntime.gdns" + + +Next Steps +---------- + +Once you are familiarized with the build process (it's as simple as running +``nake build`` after you are set up), it is recommended to go through +`godotmacros `_ and `godotnim `_ module +documentations. They describe special macros and procedures needed to define +or instantiate Godot objects. After you learned that, the rest is similar to +using any Nim library. These bindings do not limit any of Nim's capabilities, +and you can use any Nim types as fields or parameters of Godot objects and +their procedures (but, obviously, you may not be able to export some of them +to Godot editor or GDScript, unless you define your own converters). + + +Modules +======= + +The binding library consists of three major modules: + +* `godot <#modules-godot-module>`_ - Contains core types and macro definitions. + You need to import this in any module that defines or makes use of Godot + types. + +* `godotinternal <#modules-godotinternal-module>`_ - Contains raw wrappers over + few core types, such as ``GodotVariant``, ``GodotString``, ``GodotNodePath``, + ``GodotDictionary``, pool arrays. These are used by ``godotapigen`` and macro + implementations, and you don't have to use them at all in your code, unless + you want to go into low-level details for some reason. Each of those types + needs to be destructed manually with ``deinit`` procedure. + +* `godotapigen `_ - Wrapper generator based on data from + Godot's ``ClassDB``. You only need to use it as a part of the build process. + + +godot Module +------------ + +Contains core types and macro definitions. You need to import this in any +module that defines or makes use of Godot types. The sumbodules below are +exported and you don't have to import any of them directly. + +* `godotnim `_ Defines ``NimGodotObject`` and Varaint converters + for standard Nim types. +* `godotmacros `_ Defines ``gdobj`` macro for defining + Godot objects. +* `variants `_ ``Variant`` type represents a "dynamic object" + that many Godot procedures make use of. +* `arrays `_ Defines ``Array`` of Variants. +* `basis `_ Defines 3D ``Basis``. +* `colors `_ Defines ARGB ``Color``. +* `dictionaries `_ Defines ``Variant`` -> ``Variant`` + ``Dictionary``. +* `nodepaths `_ Defines ``NodePath`` - a path to a ``Node``. +* `planes `_ Defines 3D ``Plane``. +* `poolarrays `_ Defines pooled arrays: ``PoolByteArray``, + ``PoolIntArray``, ``PoolRealArray``, ``PoolVector2Array``, + ``PoolVector3Array``, ``PoolColorArray``, ``PoolStringArray``. +* `quats `_ Defines ``Quat`` (quaternion) describing object + rotation in 3D space. +* `rect2 `_ Defines ``Rect2`` - a 2D rectangle. +* `rect3 `_ Defines ``Rect3`` - a 3D box. +* `rids `_ Defines ``RID`` - a resource identifier. +* `transform2d `_ Defines ``Transform2D``. +* `transforms `_ Defines ``Transform``. +* `vector2 `_ Defines ``Vector2``. +* `vector3 `_ Defines ``Vector3``. +* `godotbase `_ Defines ``Error`` type and few common math + procedures missing in Nim's standard library. + + +Godot API +--------- + +This is an auto-generated list of Godot API modules. It's built from Godot +changeset `$GODOTAPI_CHANGESET_HASH +`_. + +$AUTO_GENERATED_GODOTAPI_LIST + + +godotinternal Module +-------------------- + +Contains low-level wrappers over Godot types that require manual memory +management. This module is used within ``godot-nim`` implementation and you +don't need to import it unless you know what you are doing. + +* `godotdictionaries `_ +* `godotnodepaths `_ +* `godotpoolarrays `_ +* `godotstrings `_ +* `godotvariants `_ diff --git a/docs/nim.cfg b/docs/nim.cfg new file mode 100644 index 0000000..9d57ecf --- /dev/null +++ b/docs/nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/godot.nimble b/godot.nimble index 1788bf7..fb29aea 100644 --- a/godot.nimble +++ b/godot.nimble @@ -1,4 +1,4 @@ -version = "0.3.0" +version = "0.4.0" author = "Xored Software, Inc." description = "Godot Engine bindings" license = "MIT" diff --git a/godot/apigen/apigen.nim b/godot/apigen/apigen.nim deleted file mode 100644 index 48c2348..0000000 --- a/godot/apigen/apigen.nim +++ /dev/null @@ -1,3 +0,0 @@ -{.warning: "'apigen.apigen' module is deprecated and will be removed in v0.4.0. Use 'godotapigen' instead.".} - -include "../godotapigen.nim" \ No newline at end of file diff --git a/godot/core/dictionaries.nim b/godot/core/dictionaries.nim index 6be9bf6..4c96817 100644 --- a/godot/core/dictionaries.nim +++ b/godot/core/dictionaries.nim @@ -1,7 +1,8 @@ # Copyright 2017 Xored Software, Inc. import hashes -import internal / [godotstrings, godotdictionaries, godotvariants] +import internal.godotstrings, internal.godotdictionaries, + internal.godotvariants type Dictionary* = ref object diff --git a/godot/core/nim.cfg b/godot/core/nim.cfg new file mode 100644 index 0000000..f1fb6a7 --- /dev/null +++ b/godot/core/nim.cfg @@ -0,0 +1 @@ +--path:"$projectdir/../" \ No newline at end of file diff --git a/godot/core/rids.nim b/godot/core/rids.nim index 82a5154..aece8c5 100644 --- a/godot/core/rids.nim +++ b/godot/core/rids.nim @@ -2,7 +2,7 @@ import internal.godotobjects type RID* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] proc initRID(dest: var RID) {. importc: "godot_rid_new".} diff --git a/godot/core/variants.nim b/godot/core/variants.nim index f1efd9e..6e875c3 100644 --- a/godot/core/variants.nim +++ b/godot/core/variants.nim @@ -1,7 +1,7 @@ import tables -import internal / [godotobjects, godotvariants, godotpoolarrays, godotstrings, - godotnodepaths] +import internal.godotobjects, internal.godotvariants, internal.godotpoolarrays, + internal.godotstrings, internal.godotnodepaths type Variant* = ref object diff --git a/godot/godot.nim b/godot/godot.nim index bf7eaa5..3514ea2 100644 --- a/godot/godot.nim +++ b/godot/godot.nim @@ -1,13 +1,17 @@ # Copyright 2017 Xored Software, Inc. import core.godotbase, godotinternal -import core / [vector2, rect2, vector3, transform2d, planes, quats] -import core / [rect3, basis, transforms, colors, nodepaths, rids] -import core / [dictionaries, arrays, poolarrays, variants] +import core.vector2, core.rect2, core.vector3, core.transform2d, core.planes, + core.quats +import core.rect3, core.basis, core.transforms, core.colors, core.nodepaths, + core.rids +import core.dictionaries, core.arrays, core.poolarrays, core.variants -import nim / [godotmacros, godotnim] +import nim.godotmacros, nim.godotnim export godotbase, vector2, rect2, vector3, transform2d, planes, quats, rect3, basis, transforms, colors, nodepaths, rids, dictionaries, arrays, poolarrays, variants export godotmacros, godotnim + +export GodotPropertyHint, GodotPropertyUsageFlags \ No newline at end of file diff --git a/godot/godotapigen.nim b/godot/godotapigen.nim index 42b3ea3..510251d 100644 --- a/godot/godotapigen.nim +++ b/godot/godotapigen.nim @@ -1,6 +1,6 @@ # Copyright 2017 Xored Software, Inc. -import streams, json, ospaths, strutils, times, sets, tables +import streams, json, os, strutils, times, sets, tables import sequtils, algorithm import compiler / [ast, renderer, idents, astalgo] @@ -261,8 +261,8 @@ const standardTypes = toSet( "PoolByteArray", "PoolIntArray", "PoolRealArray", "PoolStringArray", "PoolVector2Array", "PoolVector3Array", "PoolColorArray"]) -const smallIntTypes = toSet(["uint8", "int8", "uint16", "int16", "uint32", "int32", - "cint", "int", "Error"]) +const smallIntTypes = toSet(["uint8", "int8", "uint16", "int16", "uint32", + "int32", "cint", "int", "Error"]) const int64Types = toSet(["uint64", "int64"]) const intTypes = union(smallIntTypes, int64Types) const float64Types = toSet(["float64", "cdouble"]) @@ -339,6 +339,11 @@ proc doGenerateMethod(tree: PNode, methodBindRegistry: var HashSet[string], var body: PNode if not withImplementation: body = newNode(nkEmpty) + elif godotMethodName == "_init" and typ.godotName == "Object": + # It's not real (calling it crashes) and will be removed later, + # but we use init() convention in Nim, so it needs to work. + body = newNode(nkStmtList).addChain( + newNode(nkDiscardStmt).addChain(newNode(nkEmpty))) else: var methodBindName = toNimStyle( typ.name & "_" & godotMethodName & "_method_bind") @@ -410,7 +415,7 @@ proc doGenerateMethod(tree: PNode, methodBindRegistry: var HashSet[string], let isStandardType = arg.typ in standardTypes let isWrappedType = arg.typ in wrapperTypes let convArg = if not varargsName.isNil: - getInternalPtr(newCall("toGodot", argName), "Variant") + getInternalPtr(newCall("toVariant", argName), "Variant") elif isWrappedType: getInternalPtr(argName, arg.typ) elif isStandardType: newCall("unsafeAddr", argName) elif arg.typ == "string": newCall("unsafeAddr", @@ -440,7 +445,8 @@ proc doGenerateMethod(tree: PNode, methodBindRegistry: var HashSet[string], ) else: argConversions.add(newNode(nkVarSection).addChain( - newIdentDefs(ident("idx"), newNode(nkEmpty), newIntLit(staticArgsLen)))) + newIdentDefs(ident("idx"), newNode(nkEmpty), + newIntLit(staticArgsLen)))) let argLoop = newNode(nkWhileStmt) argConversions.add(argLoop) argLoop.add(newInfix(ident("idx"), ident("<"), argLenNode) @@ -544,7 +550,8 @@ proc doGenerateMethod(tree: PNode, methodBindRegistry: var HashSet[string], if isVariantRet: let convErrDef = newNode(nkLetSection).addChain( newIdentDefs(ident("convErr"), newNode(nkEmpty), - newCall("fromGodot", ident("result"), newCall("newVariant", retValIdent)))) + newCall("fromVariant", ident("result"), + newCall("newVariant", retValIdent)))) let convCheck = newIfStmt( newInfix(ident("convErr"), ident("!="), newDotExpr(ident("ConversionResult"), ident("OK"))), @@ -726,7 +733,7 @@ proc shouldExport(typ: GodotType, types: Table[string, GodotType]): bool = curTyp = types.getOrDefault(curTyp.baseName) result = curTyp.isNil -proc genTypeFile*(types: var Table[string, GodotType], targetDir: string) = +proc genTypeFile(types: var Table[string, GodotType], targetDir: string) = for typ in types.values(): types.incDerivedCount(typ.baseName) @@ -755,6 +762,9 @@ proc genTypeFile*(types: var Table[string, GodotType], targetDir: string) = renderModule(godotApiTypesTree, targetDir / "godottypes.nim") proc genApi*(targetDir: string, apiJsonFile: string) = + ## Generates .nim files in the ``targetDir`` based on the ``apiJsonFile``. + ## The JSON file can be generated by executing Godot with + ## ``--gdnative-generate-json-api `` parameters. let apiJson = parseJson(newFileStream(apiJsonFile), apiJsonFile.extractFilename()) var megaImport = newStringOfCap(64 * 1024) diff --git a/godot/godotinternal.nim b/godot/godotinternal.nim index 0fd8cb9..754900a 100644 --- a/godot/godotinternal.nim +++ b/godot/godotinternal.nim @@ -1,8 +1,8 @@ # Copyright 2017 Xored Software, Inc. -import internal / [ - godotobjects, godotarrays, godotnodepaths, godotpoolarrays, - godotstrings, godotvariants, godotdictionaries] +import internal.godotobjects, internal.godotarrays, internal.godotnodepaths, + internal.godotpoolarrays, internal.godotstrings, internal.godotvariants, + internal.godotdictionaries export godotobjects, godotarrays, godotnodepaths, godotpoolarrays, godotstrings, godotvariants, godotdictionaries @@ -206,7 +206,7 @@ type proc godotScriptRegisterProperty*(libHandle: pointer; name: cstring; path: cstring; - attr: var GodotPropertyAttributes; + attr: ptr GodotPropertyAttributes; setFunc: GodotPropertySetFunc; getFunc: GodotPropertyGetFunc) {. importc: "godot_nativescript_register_property".} @@ -270,6 +270,9 @@ proc getClassName*(o: ptr GodotObject): string = # There are physics type not known by ClassDB result = result[0..result.len-3] +proc getGodotSingleton*(name: cstring): ptr GodotObject {. + importc: "godot_global_get_singleton".} + # System Functions proc godotAlloc*(bytes: cint): pointer {. diff --git a/godot/internal/godotarrays.nim b/godot/internal/godotarrays.nim index f63f3ce..ac53bfb 100644 --- a/godot/internal/godotarrays.nim +++ b/godot/internal/godotarrays.nim @@ -2,7 +2,7 @@ type GodotArray* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] import hashes import godotobjects, godotpoolarrays, godotvariants, godotstrings diff --git a/godot/internal/godotdictionaries.nim b/godot/internal/godotdictionaries.nim index 24fee10..d00d60c 100644 --- a/godot/internal/godotdictionaries.nim +++ b/godot/internal/godotdictionaries.nim @@ -2,7 +2,7 @@ type GodotDictionary* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] import godotarrays, godotvariants, godotstrings diff --git a/godot/internal/godotnodepaths.nim b/godot/internal/godotnodepaths.nim index d05e175..2a84fe3 100644 --- a/godot/internal/godotnodepaths.nim +++ b/godot/internal/godotnodepaths.nim @@ -4,7 +4,7 @@ import godotstrings type GodotNodePath* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] proc initGodotNodePath*(dest: var GodotNodePath; s: GodotString) {. importc: "godot_node_path_new".} diff --git a/godot/internal/godotpoolarrays.nim b/godot/internal/godotpoolarrays.nim index ab4504c..98d61bc 100644 --- a/godot/internal/godotpoolarrays.nim +++ b/godot/internal/godotpoolarrays.nim @@ -2,22 +2,22 @@ type GodotPoolByteArray* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] GodotPoolIntArray* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] GodotPoolRealArray* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] GodotPoolStringArray* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] GodotPoolVector2Array* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] GodotPoolVector3Array* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] GodotPoolColorArray* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] import godotstrings, godotarrays -import core / [godotbase, colors, vector2, vector3] +import core.godotbase, core.colors, core.vector2, core.vector3 # byte diff --git a/godot/internal/godotstrings.nim b/godot/internal/godotstrings.nim index 9477ea9..f47a174 100644 --- a/godot/internal/godotstrings.nim +++ b/godot/internal/godotstrings.nim @@ -2,7 +2,7 @@ type GodotString* {.byref.} = object - p: pointer + data: array[sizeof(int), byte] proc initGodotString(dest: var GodotString) {. importc: "godot_string_new".} diff --git a/godot/internal/godotvariants.nim b/godot/internal/godotvariants.nim index 39d36eb..81ce797 100644 --- a/godot/internal/godotvariants.nim +++ b/godot/internal/godotvariants.nim @@ -46,7 +46,7 @@ type expected*: VariantType GodotVariant* {.byref.} = object - data: array[16 + sizeof(int), byte] + data: array[4 + sizeof(int) div 4, float32] import godotobjects, godotarrays, godotpoolarrays, godotstrings, godotnodepaths, godotdictionaries diff --git a/godot/internal/nim.cfg b/godot/internal/nim.cfg new file mode 100644 index 0000000..f1fb6a7 --- /dev/null +++ b/godot/internal/nim.cfg @@ -0,0 +1 @@ +--path:"$projectdir/../" \ No newline at end of file diff --git a/godot/nim/godotmacros.nim b/godot/nim/godotmacros.nim index fd7def7..e0b81bc 100644 --- a/godot/nim/godotmacros.nim +++ b/godot/nim/godotmacros.nim @@ -32,6 +32,19 @@ type ParseError = object of Exception +proc godotToNim[T](val: Variant): (T, ConversionResult) = + mixin fromVariant + result[1] = fromVariant(result[0], val) + +proc nimToGodot[T](val: T): Variant = + mixin toVariant + when compiles(toVariant(val)): + result = toVariant(val) + else: + printError("Failed to convert Nim value of type " & T.name & + " into Variant") + initNilVariant(result) + template parseError(node: NimNode, msg: string) = raise newException(ParseError, lineinfo(node) & ": " & msg) @@ -249,7 +262,7 @@ macro invokeVarArgs(procIdent, objIdent; if hasReturnValue: let theCall = newNimNode(nnkBracketExpr).add(newNimNode(nnkDotExpr).add( - newCall("toGodot", invocation), ident("godotVariant"))) + newCall("toVariant", invocation), ident("godotVariant"))) branchBody.add(getAst(initGodotVariantCall(ident("result"), theCall))) else: branchBody.add(invocation) @@ -291,11 +304,18 @@ proc nimDestroyRefFunc(obj: ptr GodotObject, methData: pointer, # so nothing to do here. discard -proc refcountIncremented*(obj: NimGodotObject) = - GC_ref(obj) +proc refcountIncremented(obj: ptr GodotObject, methodData: pointer, + userData: pointer, numArgs: cint, + args: var array[MAX_ARG_COUNT, ptr GodotVariant]): + GodotVariant {.noconv.} = + GC_ref(cast[NimGodotObject](userData)) -proc refcountDecremented*(obj: NimGodotObject): bool = - GC_unref(obj) +proc refcountDecremented(obj: ptr GodotObject, methodData: pointer, + userData: pointer, numArgs: cint, + args: var array[MAX_ARG_COUNT, ptr GodotVariant]): + GodotVariant {.noconv.} = + GC_unref(cast[NimGodotObject](userData)) + initGodotVariant(result, false) # destroy when Nim decides template registerGodotClass(classNameIdent, classNameLit, isRef, baseNameLit, createFuncIdent; isTool: bool) = @@ -306,6 +326,8 @@ template registerGodotClass(classNameIdent, classNameLit, isRef, nimObj.setGodotObject(obj) GC_ref(nimObj) result = cast[pointer](nimObj) + when compiles(nimObj.init()): + nimObj.init() let createFuncObj = GodotInstanceCreateFunc( createFunc: createFuncIdent @@ -356,19 +378,23 @@ template registerGodotField(classNameLit, classNameIdent, propNameLit, getFunc: getFuncIdent ) {.push warning[ProveInit]: off.} # false warning, Nim bug - var attr = GodotPropertyAttributes( - typ: ord(godotVariantType(propTypeIdent)), - hintString: hintTipLit.toGodotString(), + var hintStr = hintTipLit.toGodotString() + let attr = GodotPropertyAttributes( + typ: ord(variantType(propTypeIdent)), + hintString: hintStr, hint: GodotPropertyHint.hintIdent, usage: GodotPropertyUsageFlags.usageIdent ) {.push warning[ProveInit]: on.} when hasDefaultValue: - attr.defaultValue = (defaultValueNode).toGodot() + attr.defaultValue = (defaultValueNode).toVariant().godotVariant[] godotScriptRegisterProperty(getNativeLibHandle(), classNameLit, propNameLit, - attr, setFunc, getFunc) + unsafeAddr attr, setFunc, getFunc) + hintStr.deinit() + static: - import strutils, sets + import strutils, sets, sequtils + proc toGodotStyle(s: string): string {.compileTime.} = result = newStringOfCap(s.len + 10) for c in s: @@ -469,7 +495,6 @@ proc genType(obj: ObjectDecl): NimNode {.compileTime.} = GodotVariant {.noconv.} = let self = cast[classNameIdent](userData) const isStaticCall = methodNameLit == cstring"_ready" or - methodNameLit == cstring"_init" or methodNameLit == cstring"_enter_tree" or methodNameLit == cstring"_exit_tree" or methodNameLit == cstring"_enter_world" or @@ -520,25 +545,70 @@ proc genType(obj: ObjectDecl): NimNode {.compileTime.} = if isRef: # add ref/unref for types inherited from Reference - let noArgs = newSeq[NimNode]() - result.add(getAst( - registerGodotMethod(classNameLit, classNameIdent, ident("refcountIncremented"), - cstring"_refcount_incremented", 0, 0, - noArgs, genSym(nskProc, "refcount_incremented"), - ident("false")))) - result.add(getAst( - registerGodotMethod(classNameLit, classNameIdent, ident("refcountDecremented"), - cstring"_refcount_decremented", 0, 0, - noArgs, genSym(nskProc, "refcount_decremented"), - ident("true")))) + template registerRefIncDec(classNameLit) = + let refInc = GodotInstanceMethod( + meth: refcountIncremented + ) + let refDec = GodotInstanceMethod( + meth: refcountDecremented + ) + godotScriptRegisterMethod(getNativeLibHandle(), classNameLit, + cstring"_refcount_incremented", + GodotMethodAttributes(), refInc) + godotScriptRegisterMethod(getNativeLibHandle(), classNameLit, + cstring"_refcount_decremented", + GodotMethodAttributes(), refDec) + result.add(getAst(registerRefIncDec(classNameLit))) {.push warning[Deprecated]: off.} -# immediate macros are deprecated, but `untyped` doesnt make it immediate, +# immediate macros are deprecated, but `untyped` doesn't make it immediate, # as the warning and the documentation claim. macro gdobj*(definition: untyped, body: typed): typed {.immediate.} = + ## Generates Godot type. Self-documenting example: + ## + ## .. code-block:: nim + ## import godot, node + ## + ## gdobj MyObj of Node: + ## var myField: int + ## ## Not exported to Godot (i.e. editor will not see this field). + ## + ## var myString* {.gdExport, hint: Length, tip: "20".}: string + ## ## Exported to Godot as ``my_string``. + ## ## Editor will limit this string to length 20. + ## ## ``hint` is a value of ``GodotPropertyHint`` enum. + ## ## ``tip`` depends on the value of ``hint``, its format is described + ## ## in ``GodotPropertyHint`` documentation. + ## + ## method ready*() = + ## ## Exported methods are exported to Godot by default, + ## ## and their Godot names are prefixed with ``_`` + ## ## (in this case ``_ready``) + ## print("I am ready! myString is: " & myString) + ## + ## proc myProc*() {.gdExport.} = + ## ## Exported to Godot as ``my_proc`` + ## print("myProc is called! Incrementing myField.") + ## inc myField + ## + ## If parent type is omitted, the type is inherited from ``Object``. + ## + ## ``tool`` specifier can be added to mark the type as an + ## `editor plugin `_: + ## + ## .. code-block:: nim + ## import godot, editor_plugin + ## + ## gdobj(MyTool of EditorPlugin, tool): + ## method enterTree*() = + ## print("MyTool initialized!") + ## + ## Objects can be instantiated by invoking + ## `gdnew `_ or by using + ## `load `_ or any other way + ## that you can find in `Godot API `_. let typeDef = parseType(definition, callsite()) result = genType(typeDef) {.push warning[Deprecated]: on.} - diff --git a/godot/nim/godotnim.nim b/godot/nim/godotnim.nim index 7639c78..5d26891 100644 --- a/godot/nim/godotnim.nim +++ b/godot/nim/godotnim.nim @@ -10,15 +10,28 @@ import core.vector2, core.rect2, core.arrays, core.poolarrays, core.variants import godotinternal -## Definition of NimGodotObject and converters for built-in types. +## 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 variantType*(T: typedesc[MyType]): VariantType +## proc toVariant*(self: MyType): Variant +## proc fromVariant*(self: var MyType, val: Variant): ConversionResult type NimGodotObject* = ref object of RootObj + ## The base type all Godot types inherit from. + ## Manages lifecycle of the wrapped GodotObject. godotObject: ptr GodotObject isExternalRef: bool isOwn: bool ConversionResult* {.pure.} = enum + ## Conversion result to return from ``fromVariant`` procedure. OK, TypeError, ## Type mismatch @@ -41,7 +54,11 @@ type 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.} @@ -49,20 +66,24 @@ type isNative: bool isRef: bool -template printWarning*(warn: typed) = +template printWarning*(warning: typed) = + ## Prints ``warning`` to Godot log, adding filename and line information. let (filename, line) = instantiationInfo() - godotPrintWarning(cstring($warn), nil, cstring(filename), line.cint) + godotPrintWarning(cstring($warning), nil, cstring(filename), line.cint) -template printError*(err: typed) = +template printError*(error: typed) = + ## Prints ``error`` to Godot log, adding filename and line information. let (filename, line) = instantiationInfo() - godotPrintError(cstring($err), nil, cstring(filename), line.cint) + godotPrintError(cstring($error), nil, cstring(filename), line.cint) proc print*(message: string) {.inline.} = + ## Prints ``message`` to Godot log. var s = message.toGodotString() godotPrint(s) s.deinit() proc print*(message: cstring) {.inline.} = + ## Prints ``message`` to Godot log. var s = message.toGodotString() godotPrint(s) s.deinit() @@ -141,7 +162,9 @@ macro isReference(T: typedesc): bool = else: ident("false") template registerClass*(T: typedesc; godotClassName: cstring, - isNativeParam: bool) = + native: bool) = + ## Registers the specified Godot type. + ## Used by ``gdobj`` macro and `godotapigen `_. if classRegistry.isNil: classRegistry = newTable[cstring, ObjectInfo]() let constructor = proc(): NimGodotObject = @@ -154,21 +177,21 @@ template registerClass*(T: typedesc; godotClassName: cstring, let objInfo = ObjectInfo( constructor: constructor, baseNativeClass: base, - isNative: isNativeParam, + isNative: native, isRef: isRef ) classRegistry[godotClassName] = objInfo static: let objInfoStatic = ObjectInfo( baseNativeClass: base, - isNative: isNativeParam, - isRef: isRef + isNative: native, + isRef: isRef, ) classRegistryStatic[godotClassName] = objInfoStatic when isRef: static: refClasses.add(T.name) - when isNativeParam: + when native: static: nativeClasses.add(T.name) @@ -187,6 +210,9 @@ proc newNimGodotObject[T: NimGodotObject]( result.godotObject.reference() proc asNimGodotObject*[T: NimGodotObject](godotObject: ptr GodotObject): 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: @@ -202,15 +228,24 @@ 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 or not (obj of T): when not defined(release): if not obj.isNil: @@ -220,9 +255,6 @@ proc `as`*[T: NimGodotObject](obj: NimGodotObject, t: typedesc[T]): T = else: result = T(obj) -proc getSingletonGodot*(name: cstring): ptr GodotObject {. - importc: "godot_global_get_singleton".} - proc newRStrLit(s: string): NimNode {.compileTime.} = result = newNimNode(nnkRStrLit) result.strVal = s @@ -241,8 +273,11 @@ macro toGodotName(T: typedesc): cstring = ident("cstring"), newRStrLit(godotName)) 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 = toGodotName(T) - let singleton = getSingletonGodot(godotName) + let singleton = getGodotSingleton(godotName) if singleton.isNil: printError("Tried to get non-existing singleton of type " & $godotName) else: @@ -285,6 +320,7 @@ proc newOwnObj[T: NimGodotObject](name: cstring): T = ret.deinit() proc gdnew*[T: NimGodotObject](): T = + ## Instantiates new object of type ``T``. const godotName = toGodotName(T) const objInfo = classRegistryStatic[godotName] result = when objInfo.isNative: @@ -292,6 +328,7 @@ proc gdnew*[T: NimGodotObject](): T = else: newOwnObj[T](godotName) proc newCallError*(err: VariantCallError): ref CallError = + ## Instantiates ``CallError`` from Godot ``err``. let msg = case err.error: of VariantCallErrorType.OK, VariantCallErrorType.InvalidMethod, @@ -307,6 +344,8 @@ proc newCallError*(err: VariantCallError): ref CallError = 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" @@ -317,28 +356,32 @@ proc newConversionError*(err: ConversionResult): ref ValueError = result = newException(ValueError, msg) -proc removeGodotObject*(nimObj: NimGodotObject) {.inline.} = - ## Used from destructor - nimObj.godotObject = nil - proc setGodotObject*(nimObj: NimGodotObject, obj: ptr GodotObject) {.inline.} = + ## Used from Godot constructor. Don't call this. assert(not obj.isNil) assert(nimObj.godotObject.isNil) # reassignment is not allowed nimObj.godotObject = obj proc setOwn*(nimObj: NimGodotObject) {.inline.} = + ## Used from Godot constructor. Don't call this. nimObj.isOwn = true +proc removeGodotObject*(nimObj: NimGodotObject) {.inline.} = + ## Used from Godot destructor. Don't call this. + nimObj.godotObject = nil + proc godotObject*(nimObj: NimGodotObject): ptr GodotObject {.inline.} = + ## Returns internal poitner to ``GodotObject``. Use only if you know what + ## you are doing. nimObj.godotObject -proc godotVariantType*(T: typedesc[NimGodotObject]): VariantType {.inline.} = +proc variantType*(T: typedesc[NimGodotObject]): VariantType {.inline.} = VariantType.Object -proc toGodot*(self: NimGodotObject): Variant {.inline.} = +proc toVariant*(self: NimGodotObject): Variant {.inline.} = newVariant(self.godotObject) -proc fromGodot*[T: NimGodotObject](self: var T, +proc fromVariant*[T: NimGodotObject](self: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Object: let objPtr = val.asGodotObject() @@ -350,27 +393,27 @@ proc fromGodot*[T: NimGodotObject](self: var T, else: result = ConversionResult.TypeError -proc godotVariantType*(T: typedesc[enum]): VariantType {.inline.} = +proc variantType*(T: typedesc[enum]): VariantType {.inline.} = VariantType.Int -proc toGodot*[T: enum](self: T): Variant {.inline.} = +proc toVariant*[T: enum](self: T): Variant {.inline.} = newVariant(int64(ord(self))) -proc fromGodot*[T: enum](self: var T, +proc fromVariant*[T: enum](self: var T, val: Variant): ConversionResult {.inline.} = if val.getType() == VariantType.Int: self = T(val.asInt()) else: result = ConversionResult.TypeError -proc toGodot*(self: Variant): Variant {.inline.} = +proc toVariant*(self: Variant): Variant {.inline.} = self -proc fromGodot*(self: var Variant, +proc fromVariant*(self: var Variant, val: Variant): ConversionResult {.inline.} = self = newVariant(val) -proc godotVariantType*(T: typedesc[SomeGodotOrNum]): VariantType {.inline.} = +proc variantType*(T: typedesc[SomeGodotOrNum]): VariantType {.inline.} = when T is SomeSignedInt or T is SomeUnsignedInt: VariantType.Int elif T is bool: @@ -426,10 +469,10 @@ proc godotVariantType*(T: typedesc[SomeGodotOrNum]): VariantType {.inline.} = else: VariantType.Nil -proc toGodot*[T: SomeGodotOrNum](val: T): Variant {.inline.} = +proc toVariant*[T: SomeGodotOrNum](val: T): Variant {.inline.} = newVariant(val) -proc fromGodot*[T: SomeSignedInt or SomeUnsignedInt]( +proc fromVariant*[T: SomeSignedInt or SomeUnsignedInt]( self: var T, val: Variant): ConversionResult = if val.getType() != VariantType.Int: result = ConversionResult.TypeError @@ -443,7 +486,7 @@ proc fromGodot*[T: SomeSignedInt or SomeUnsignedInt]( else: self = T(intVal) -proc fromGodot*[T: SomeFloat](self: var T, val: Variant): ConversionResult = +proc fromVariant*[T: SomeFloat](self: var T, val: Variant): ConversionResult = if val.getType() == VariantType.Real: self = T(val.asReal()) elif val.getType() == VariantType.Int: @@ -451,8 +494,8 @@ proc fromGodot*[T: SomeFloat](self: var T, val: Variant): ConversionResult = else: result = ConversionResult.TypeError -proc fromGodot*[T: SomeGodot](self: var T, val: Variant): ConversionResult = - if godotVariantType(T) != val.getType(): +proc fromVariant*[T: SomeGodot](self: var T, val: Variant): ConversionResult = + if variantType(T) != val.getType(): return ConversionResult.TypeError when self is bool: self = val.asBool() @@ -506,13 +549,13 @@ proc fromGodot*[T: SomeGodot](self: var T, val: Variant): ConversionResult = # mustn't reach this result = ConversionError.TypeError -proc godotVariantType*(T: typedesc[string]): VariantType {.inline.} = +proc variantType*(T: typedesc[string]): VariantType {.inline.} = VariantType.String -proc toGodot*(s: string): Variant {.inline.} = +proc toVariant*(s: string): Variant {.inline.} = newVariant(s) -proc fromGodot*(s: var string, val: Variant): ConversionResult = +proc fromVariant*(s: var string, val: Variant): ConversionResult = if val.getType() == VariantType.String: s = val.asString() elif val.getType() == VariantType.Nil: @@ -520,20 +563,20 @@ proc fromGodot*(s: var string, val: Variant): ConversionResult = else: result = ConversionResult.TypeError -proc godotVariantType*(T: typedesc[seq]): VariantType = +proc variantType*(T: typedesc[seq]): VariantType = VariantType.Array -proc godotVariantType*(T: typedesc[array]): VariantType = +proc variantType*(T: typedesc[array]): VariantType = VariantType.Array -proc toGodot*[T](s: openarray[T]): Variant = +proc toVariant*[T](s: openarray[T]): Variant = var arr = newArray() mixin toGodot for item in s: - arr.add(toGodot(item)) + arr.add(toVariant(item)) result = newVariant(arr) -proc fromGodot*[T](s: var seq[T], val: Variant): ConversionResult = +proc fromVariant*[T](s: var seq[T], val: Variant): ConversionResult = if val.getType() != VariantType.Array: result = ConversionResult.TypeError else: @@ -541,12 +584,12 @@ proc fromGodot*[T](s: var seq[T], val: Variant): ConversionResult = s = newSeq[T](arr.len) for idx, item in arr: mixin fromGodot - let convResult = fromGodot(s[idx], item) + let convResult = fromVariant(s[idx], item) if convResult != ConversionResult.OK: s = nil return convResult -proc fromGodot*[T: array](s: var T, val: Variant): ConversionResult = +proc fromVariant*[T: array](s: var T, val: Variant): ConversionResult = if val.getType() != VariantType.Array: result = ConversionResult.TypeError else: @@ -555,21 +598,21 @@ proc fromGodot*[T: array](s: var T, val: Variant): ConversionResult = return ConversionResult.TypeError for idx, item in arr: mixin fromGodot - let convResult = fromGodot(s[idx], item) + let convResult = fromVariant(s[idx], item) if convResult != ConversionResult.OK: return convResult -proc godotVariantType*(T: typedesc[Table|TableRef]): VariantType {.inline.} = +proc variantType*(T: typedesc[Table|TableRef]): VariantType {.inline.} = VariantType.Dictionary -proc toGodot*[T: Table or TableRef](t: T): Variant = +proc toVariant*[T: Table or TableRef](t: T): Variant = var dict = newDictionary() mixin toGodot for k, v in t.pairs(): - dict[toGodot(k)] = toGodot(v) + dict[toVariant(k)] = toVariant(v) result = newVariant(dict) -proc fromGodot*[T: Table or TableRef](t: var T, val: Variant): ConversionResult = +proc fromVariant*[T: Table or TableRef](t: var T, val: Variant): ConversionResult = if val.getType() != VariantType.Dictionary: result = ConversionResult.TypeError else: @@ -582,31 +625,18 @@ proc fromGodot*[T: Table or TableRef](t: var T, val: Variant): ConversionResult for k, v in dict: var nimKey: type(t.keys()) var nimVal: type(t.values()) - let keyResult = fromGodot(nimKey, k) + let keyResult = fromVariant(nimKey, k) if keyResult != ConversionResult.OK: when t is ref: t = nil return keyResult - let valResult = fromGodot(nimVal, v) + let valResult = fromVariant(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); @@ -614,6 +644,8 @@ N_NOINLINE(void, setStackBottom)(void* thestackbottom); var nativeLibHandle: pointer proc getNativeLibHandle*(): pointer = + ## Returns NativeScript library handle used to register type information + ## in Godot. Use only if you know what you are doing. return nativeLibHandle proc godot_nativescript_init(handle: pointer) {. diff --git a/godot/nim/nim.cfg b/godot/nim/nim.cfg new file mode 100644 index 0000000..f1fb6a7 --- /dev/null +++ b/godot/nim/nim.cfg @@ -0,0 +1 @@ +--path:"$projectdir/../" \ No newline at end of file diff --git a/godot/nimdoc.cfg b/godot/nimdoc.cfg new file mode 100644 index 0000000..d4e8565 --- /dev/null +++ b/godot/nimdoc.cfg @@ -0,0 +1,5 @@ +doc.item.seesrc = """  Source +Edit +""" \ No newline at end of file