I've tried to convert https://github.com/pragmagic/godot-nim for use with orc. And while it's been working for the most part, I'm getting a crash in a destructor that I've mechanically converted from a finalizer. godot-nim relies on gc:refc and when converting to orc I had to convert the finalizer for this ref type:
type
NimGodotObject* = ref object of RootObj
## The base type all Godot types inherit from.
## Manages lifecycle of the wrapped ``GodotObject``.
godotObject: ptr GodotObject
linkedObjectPtr: pointer
## Wrapper around native object that is the container of the Nim "script"
## This is needed for `of` checks and `as` conversions to work as
## expected. For example, Nim type may inherit from ``Spatial``, but the
## script is attached to ``Particles``. In this case conversion to
## ``Particles`` is valid, but Nim type system is not aware of that.
## This works in both directions - for linked native object this
## reference points to Nim object.
## This is stored as a raw pointer to avoid reference cycles and therefore
## improve GC performance.
isRef*: bool
isFinalized: bool
isNative: bool
proc deinit*(obj: NimGodotObject) =
## Destroy the object. You only need to call this for objects not inherited
## from Reference, where manual lifecycle control is necessary.
assert(not obj.godotObject.isNil)
obj.godotObject.deinit()
obj.godotObject = ni
proc linkedObject(obj: NimGodotObject): NimGodotObject {.inline.} =
cast[NimGodotObject](obj.linkedObjectPtr)
proc nimGodotObjectFinalizer*[T: NimGodotObject](obj: T) =
if obj.godotObject.isNil or obj.isNative: return
# important to set it before so that ``unreference`` is aware
obj.isFinalized = true
if (obj.isRef or not obj.linkedObject.isNil and obj.linkedObject.isRef) and
obj.godotObject.unreference():
obj.deinit()
Here's my conversion:
type
NimGodotObject* = ref NimGodotObj
NimGodotObj = object of RootObj
## The base type all Godot types inherit from.
## Manages lifecycle of the wrapped ``GodotObject``.
godotObject: ptr GodotObject
linkedObjectPtr: pointer
## Wrapper around native object that is the container of the Nim "script"
## This is needed for `of` checks and `as` conversions to work as
## expected. For example, Nim type may inherit from ``Spatial``, but the
## script is attached to ``Particles``. In this case conversion to
## ``Particles`` is valid, but Nim type system is not aware of that.
## This works in both directions - for linked native object this
## reference points to Nim object.
## This is stored as a raw pointer to avoid reference cycles and therefore
## improve GC performance.
isRef*: bool
isFinalized: bool
isNative: bool
proc `=destroy`*(obj: var NimGodotObj) =
if obj.godotObject.isNil or obj.isNative: return
# important to set it before so that ``unreference`` is aware
obj.isFinalized = true
let linkedGodotObject = cast[NimGodotObject](obj.linkedObjectPtr)
if (obj.isRef or not linkedGodotObject.isNil and linkedGodotObject.isRef) and obj.godotObject.unreference():
obj.godotObject.deinit()
obj.godotObject = nil
In the code with the finalizer using gc:refc no crash occurs. With the destructor code and orc, I get a crash around the line where I create the let linkedGodotObject variable. Here's what the top of stacktrace looks like:
CrashHandlerException: Program crashed
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[0] unregisterCycle__rR8fldvW9aUfvKydORzL1RA (C:\nim\lib\system\orc.nim:140)
[1] rememberCycle__LoYD9cYK9aJvrcDizBN64qaQ (C:\nim\lib\system\orc.nim:448)
[2] nimDecRefIsLastCyclicDyn (C:\nim\lib\system\orc.nim:465)
[3] eqdestroy___SAYng9cJoF6y4ebaA9cSvwLg (C:\godot\gdnim\deps\godot\nim\godotnim.nim:196)
[4] removeGodotObject__t3kHGTvDIkuUnCL9ab87QOAgodotnim (C:\godot\gdnim\deps\godot\nim\godotnim.nim:197)
[5] nimDestroyFunc__FMVhGprKRYmCSltkbHn4rw (C:\godot\gdnim\deps\godot\nim\godotmacros.nim:516)
[6] NativeScriptInstance::`scalar deleting destructor'
...
#orc.nim
proc unregisterCycle(s: Cell) =
# swap with the last element. O(1)
let idx = s.rootIdx-1
when false:
if idx >= roots.len or idx < 0:
cprintf("[Bug!] %ld\n", idx)
quit 1
roots.d[idx] = roots.d[roots.len-1] # <-- crash here
roots.d[idx][0].rootIdx = idx+1
dec roots.len
s.rootIdx = 0
So am I doing something bad in the destructor, or is this a problem elsewhere?
How is this even working?
type
NimGodotObject* = ref object of RootObj
## The base type all Godot types inherit from.
## Manages lifecycle of the wrapped ``GodotObject``.
godotObject: ptr GodotObject
linkedObjectPtr: pointer
# ...
proc linkedObject(obj: NimGodotObject): NimGodotObject {.inline.} =
cast[NimGodotObject](obj.linkedObjectPtr)
Casting ref objects to unmanaged pointer and having them outlive the casting scope is a great way to get a nil pointer.
I'm not sure I follow. Isn't it the other way around, linkedObjectPtr being cast to ref object (NimGodotObject)? In any case, GC_ref() is being called on linkObjectPtr, and GC_unref() to release it.
proc setNativeObject*(nimObj: NimGodotObject,
nativeObj: NimGodotObject) {.inline.} =
## Used from Godot constructor produced by ``gdobj`` macro. Do not call.
GC_ref(nativeObj)
nimObj.linkedObjectPtr = cast[pointer](nativeObj)
nativeObj.linkedObjectPtr = cast[pointer](nimObj)
nativeObj.isNative = true
proc removeGodotObject*(nimObj: NimGodotObject) {.inline.} =
## Used from Godot destructor produced by ``gdobj`` macro. Do not call.
GC_unref(nimObj.linkedObject)
nimObj.godotObject = nil
nimObj.linkedObject.godotObject = nil
nimObj.linkedObjectPtr = nil
Hi Araq! Thanks! It feels nice to have a giant in the community acknowledge your work. :)
I think I have to install WSL on my windows machine to get access to valgrind. I've implemented a form of hot reloading with godot-nim, https://github.com/geekrelief/gdnim, and it works fine with --gc:orc.
However with orc and -d:useMalloc, I get a crash after generating lots of objects and reloading several times. I've been keeping a log of the crashes, https://github.com/geekrelief/gdnim/issues/8, but I haven't created a minimal reproducible case yet.
I am using nim devel, and I've been keeping an eye on fixes for orc and {.pure.}, for now I have work arounds for both. And I'm super excited to see what speed increases IC will bring!