gdnim is a testbed for experimental features for godot-nim projects that implements hot reloading of dlls as well as features for ease of development.
NOTE A new macro gdnim
was added to replace gdobj
for defining classes. See the gdobj
branch for the old samples.
The goal is to streamline and speed up the process of development for godot-nim by adding experimental features like:
import godotapi / [scene_tree]
on node
since node exports scene_tree
, unless you want autocompletion from nimsuggest.exit_tree
to stop console warnings. e.g.: self.myResource = nil
in exit_tree
is defined automatically.nim c build
build.ini
with the location of godot bin files. If you have your own build of godot you can configure the repo source directory and use ./build gdengine
./build help
./build prereqs
./build cleanbuild
For a new project (sample code removed), branching from master
: ./build init [branch_name]
./build gencomp my_comp node_2d
. The nim file is created for you in components
See Setup for details.components/my_comp.nim
./build -m
(-m
moves the safe dll to the hot dll path)./build gd
(if this fails, launch godot manually, or see the Setup section)_tscn/my_comp.tscn
./build cwatch
{.pure.}
. bug report./build prereqs
)
Create a component by running ./build gencomp [name] [godot_class]
. name
is the name of your component, and godot_class
is the class from which it derives. Both should be in snake_case.
For example ./build gencomp my_node node_2d
generates a my_node.nim
file in /components
. And a matching tscn, gdns, and gdnlib file in the directories specified in build.ini
.
To delete a component run: ./build delcomp [name]
.
New components are generated from template strings defined in tasks.nim
. Customize it for your needs. Remember to recompile ./build
with nim c build
with any change to tasks.nim
.
The gdnim
macro is a layer over godot-nim's gdobj
macro that interacts with the Watcher
to enable hot reloading. There are four sections to consider when setting up for hot reloading.
first:
Takes a body of code that is only run during initialization. It executes at the top of enter_tree
.dependencies:
Declares other components as dependencies. If a component instance holds a reference to other component instances, but their relationship is not declared in dependencies
hot reloading will fail when the dependency needs to reload. By declaring dependencies
, when the dependency reloads the dependent component will reload with it. See the example components color_palette
and color_palette_slot
.unload:
Used to perform data serialization for data that can't be saved as a {.gdExport.}
. Call the save()
macro to store the data with Watcher.reload:
Declares that the component is hot reloadable. This takes a body of code that is run at the top of enter_tree
, but after first
. Call the load()
macro to retrieve any data stored with Watcher via save()
.
You can choose not to define the enter_tree
method and put its code inside the reload:
section.
The proc isNewInstance()
can be used to check the reloading state of a component instance at runtime. This is useful for initialization or adding children dynamically.
You can do conditional compilation for reloading code with when defined(does_reload):
, but this currently, only works inside methods. It would require a modification to gdobj
to work in general.
A top level gdnim
macro is implemented to replace the use of gdobj
. See below for implementation details
.tscn
scene file, with the root node containing the .gdns
nativescript attached, pointing to the .nim
code file generated dynamic library. The correct setup for reloading is a hierarchy of scenes. See /app/scenes/main.tscn
and how it references the other component scenes from /app/_tscn
./app/_dlls
folder for changes.gdnim
macro to define your class.See components
for samples on how to set things up for reloading.
Disable hot reloading This will stop compilation of the Watcher, delete the Watcher files, and disable the reloading parts of the components.
build.ini
, under [Hot]
, set reload="off"
../build cleanbuild
project.godot
Autoload
so the Watcher isn't loaded. You can go through the Project settings or modify project.godot
directly.Re-enable hot reloading
build.ini
, under [Hot]
, set reload="on"
../build cleanbuild
project.godot
Autoload
so the Watcher is loaded. Select _tscn/watcher.tscn
to load../build
.nim
for hot reload../build comp compName
is the same as ./build compName
. Only component compName
is built../build
will build all changed components for hot reloading.-m
or --move
: ./build -m
builds and moves the dll from the safe to hot path. Use when the game is closed to prevent Watcher from reloading on start.-f
or --force
: ./build -f
force builds the components.--ini:custom_build.ini
: pass in your own ini file for different build configurations../build -m
to move the safe dll to the hot dll path and rerun the app../build -f comp_name
or deleting the dll and rebuilding../build cleanbuild
to rebuild the dlls from scratch.
vcc
as the compiler and set the build_kind
to debug
.
NilAccess
error in your code. Using the ifValid
macro in utils
you can wrap your code in a nil check, and it'll print to the console if a nil access is detected. You can do toggle the output with build.ini
's verbose_nil_check
.get
or get_property_list
. This might cause a crash because _get
and _get_property_list
are virtual functions in Godot. In godot-nim, Godot's _get
is mapped to godot-nim's method get
, and Godot's get
is mapped to godot-nim's getImpl
. See deps/godotapi/objects.nim
for examples.Gdnim uses a customized build script and godot engine 3.x which unloads gdnative libraries when their resource is no longer referenced. It removes the dependency on nake and nimscript, but requires you to recompile build.nim
if you change build.nim
or tasks.nim
. Gdnim also uses a custom version of the godot-nim bindings in the deps/godot directory.
build.ini
.app
: This is the godot project folder.
app/_dlls
: Location for ./build
compiled, component libraries (.dll's, .so's). If you have other dlls you want to store here, modify tasks.nim. See task cleandll
, or put your dlls somewhere else.app/_gdns
: Location for ./build gencomp
generated NativeScript files. These are checked and regenerated for each component nim file. So they're safe to delete when you want to remove a component.app/_tscn
: Location for ./build gencomp
generated tscn files. Customize these for your needs.app/scenes
: (Optional) Location for your own scenes to keep separate from _tscn.deps/app
: A blank godot project used when creating a new project with ./build init
.deps/godot
: Custom version of godot-nim bindings. You can move this and update the location in build.ini
deps/tcc
: tcc header required on Windows for asyncdispatchdeps/watcher
: scene files for Watcher. When reload
is re-enabled, the Watcher scene files are copied from here to app
.build.nim
: The build script, compiled with nim c build
, includes the tasks.nim
tasks.nim
: Build tasks are specified here for updating / compiling the godot engine, generating / compiling components, running the godot editor, etc. You can modify the templates used to generate component files up top. After modifying rebuild with nim c build
.gdnim/watcher.nim
: The Watcher node that monitors changes to registered components. In a new godot project set _tscn/watcher.tscn
to autoload in Project Settings.gdnim/gdnim.nim
: Included in every component, imports and exports godot-nim and stuff below.
gdnim/globals.nim
: Defines constants and symbols used for hot reloading and Watcher signals.gdnim/hot.nim
: The module implements the gdnim
, save
, load
and other macros to make interacting with Watcher and hot reloading easier.gdnim/utils.nim
: This module contains helper procs and macros.build.ini
: The default configuration file used to specify directories and settings. This is read when ./build
is run. A different config file can be set using the --ini
flag. Example: ./build --ini:my_config.ini cleanbuild
components
: Where .nim
component files live. Components must have unique identifiers across the project. Dlls are generated from these components.components/tools
: Where nim files for tool / editor plugins go. Generated with ./build gentool my_tool_name
Modify the build.ini
, build.nim
and tasks.nim
script for your needs. build.ini
expects some paths the godot engine repo and editor executables.
If you're not interested in building godot from source, you can use Godot 3.x and ignore the ./build gdengine
command. If you want to use ./build gd
or ./build play
to launch the editor or app fill out the paths to your editor binaries.
The rest of the settings under build.ini
's [Godot]
section are for building godot from source.
If you have all my mods from build.ini
's merge_branches in your git repo you can, run ./build gdengine update
. Otherwise stick to using godot engine 3.x.
The app
folder contains the godot project. You can use ./build init branch_name
to create a new gdnim project on a new branch.
You create "components" which are the classes that can reload by running the ./build gencomp your_module_name godot_base_class_name
. A nim file will appear in the components
folder. Generated files are stored in app/_dlls
, app/_gdns
, app/_tscn
.
See the examples in the components
folder.
See ./build help
for available tasks like downloading the godot source, compiling the engine, generating godot-nim bindings api, compiling the watcher and components, etc.
./build gd
Launches the godot editor. On Windows it spawns a terminal using Terminal. On Linux there
isn't a general way to support launching the editor from a terminal for all distributions
(as far as I know), so modify the task gd
for your system.
xxx The build system consists of build.nim
, tasks.nim
, build.nim.cfg
and build.ini
. build.nim
includes tasks.nim
and reads build.ini
at runtime. To compile the build system run: nim c build
.
Tasks are defined in tasks.nim
. You can customize it for your needs, just make sure to recompile. Changes to build.ini
are picked up when ./build
executes. Find out what tasks are available by inspecting that file or running ./build help
By default running ./build
will build any components that have changed. If you supply an argument with no task name: ./build my_comp
the argument is assumed to be a component.
Watcher monitors the app/_dlls
folder for updates and coordinates the reload process with the components. The components use the hot module save and load macros to persist data with Watcher.
NOTE Below details how to hot reloading works when using the gdobj
macro. Using the gdnim
macro reduces the boilerplate for the setup, and does all this stuff for you. See the gdobj
branch components
for samples.
To set up a component for reloading, the component needs to:
hot.register_instance
which registers the component name with the Watcher node. Typically, done when godot's Node.enter_tree()
runs, so the component can find the Watcher. The gdnim
macro defines this if you have a reload:
section.proc hot_unload(): seq[byte] {.gdExport.}
on the Node to serialize custom data. The gdnim
macro defines hot_unload
with the unload:
section.hot_unload
the hot.save()
macro is used to specify member fields to save.hot.load
macro like register_instance(comp)?.load(self.data)
. Watcher will return previously persisted data after a component is registered so the node can complete initialization.When a component is compiled it generates a library file (safe dll). If the godot editor is not in focus with the project opened the safe dll can be copied to the hot dll path. Otherwise, you'll get a warning that the dll can't be moved and reload will fail.
When the project application is running, update and build the components. Watcher will check if safe dll is newer than hot dll and start a reload if so.
The godot-nim library in deps has been customized to use the new gc:ORC and prep it for future versions of nim. This is why ORC is suitable for games: https://nim-lang.org/blog/2020/12/08/introducing-orc.html Use the build script to generate the godotapi into the deps folder. Gdnim, and the godot-nim bindings are tested against the nim devel branch 3b963a81.
Avoid the useMalloc
option with ORC. It'll eventually cause a crash.
GCC is the recommended compiler for most use cases. It supports all the features in gdnim, and is the middle of pack in terms of compilation speed.
gcc requires some additional dlls in the _dlls
folder to run. Using gcc on Windows, tasks.nim's final task checks for a couple dll's to support threading.
VCC is only available on Windows. It produces the smallest dlls, but has the longest compile times. Vcc + Visual Studio is useful for crash debugging purposes.
TCC Tiny C Compiler
TCC has the fastest compile times, but crashes when compiling with threads:on. If compiling on windows, read deps/tcc/README.md
to make tcc work with the asynchdispatch
module. Tcc is not as well supported as the other compilers, and may not support all features of gdnim.
This project is licensed under the MIT license. Read LICENSE file for details.
Copyright (c) 2018 Xored Software, Inc.
Copyright (c) 2020 Don-Duong Quach