What’s the best way to create a wrapper class for ...
# kotlin-native
k
What’s the best way to create a wrapper class for a CStruct that is rather save and wouldn’t leak (possibly?)
j
You can use
createCleaner
to run a cleanup action when your wrapper class is deallocated by GC. See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.ref/create-cleaner.html
k
You wouldn’t have any references to some project or example how to efficiently use Cleaner?
actually for a cleaner, can the Resource class be
inner
as well? Or does it have to be a standard Class only
j
I use them in my library, which I'll be open sourcing and releasing soon. You can find usage examples with a GitHub search. The resource can be any reference you want. The important thing is that the cleanup action lambda should not reference and capture any other variables. It should only use the
it
reference to the resource to release memory or perform other cleanup actions.
k
Ah thank you! This is really useful. Been trying to come up with ways to wrap around many C math structs (e.g. Vectors). Is there an actual way to find out if it cleaner is destroying the resource?
j
How a resource is cleaned up varies by API and how the resource was allocated. There might be a reference counting C API to retain and release the resource. Or if you allocate the memory manually, you may just be calling free. So it depends what you're doing how you can check if the resource has been cleaned up. Accessing memory that has been freed afterwards will often result in a runtime crash. You can also log when the cleaner is called.
k
To log it, is it to pass Platform.isCleanersLeakCheckerActive ?
j
I just mean you can log in the cleaner when it's run to confirm it's running as expected. I believe
isCleanersLeakCheckerActive
does some checks to ensure cleaners aren't leaking resources by not being run. I haven't used it.
k
I did try to print out some log in the dispose function that is passed to the cleaner, or even within the lambda but got nothing back so idk if all is good or bad
j
I would expect the cleaner to be called once you've released all references to the containing class and GC runs. You can force a GC run by calling
kotlin.native.runtime.GC.collect()
.
k
Yeah I don’t see anything coming out from the dispose function passed to lambda at all unless I’m doing it wrong.
I found that using the cleaner format was actually not a good idea as the performance tanked down like mad. Or perhaps just not good for my use case
j
What are you doing as the cleanup action in the cleaners? A simple memory release shouldn't have a performance impact. Are there many Kotlin objects being allocated and cleaned up?
k
In the cleanup action I just passed a function named dispose which frees the allocation. Unfortunately it’s a Vector struct that’s part of the c library I’m working with that is required almost everywhere
j
I'm not sure if passing a function reference is a valid usage of the cleaner. The resource reference should be a reference to whatever resource you are disposing of, rather than a function that does the disposing. Is the struct really required to be passed around in a Kotlin wrapper? Or can its usage be scoped to a call site?
k
It’s usage, alongside many math code in the C lib is kind of critical that they’re allocated (at least with my level of knowledge). It being a game framework you need to constantly update the the values. I also want to wrap them around Kotlin, but also expose the pointer so that that pointer is the central point for managing the allocated cstruct
j
You might consider if there's a way for the class that manages the references to the structs that are being updated to release the unused ones when allocating and updating them, rather than depending on a wrapper being deallocated and GC'ed to clean up the resources.
k
Not too sure how to start off with it, but I guess I will just have to mess about..
I'm so confused how this is even giving me better performance than other options I tried...
Copy code
@OptIn(ExperimentalForeignApi::class)
inline fun Vec2(x: Float = 0F, y: Float = 0F, allocator: AutofreeScope = MemScope()): Vector2 {
    return allocator.alloc<Vector2> {
        this.x = x
        this.y = y
    }
}
After doing some tests that implementation just gave me more performance than creating a class wrapper with an allocated value pointer and a cleaner that frees it (without a function) and I'm 100% positive my attempt is probably the worst thing possible, but somehow just works better than the other things I tried?
j
This function isn't ever freeing memory by default. Unless an
allocator
is passed to the function that is cleared by the caller (i.e. using
Arena
and calling
clear()
), the default
MemScope()
only allocates memory, but never frees it.
memScoped { ... }
uses a
MemScope
and calls clear at the end of its lambda scope, which you could also wrap the call to
Vec2()
by passing the
memScoped { ... }
this
reference.
k
Duh!! Thank you! I was not aware! You saved my ass here, I thought it's a real thing that releases for you like memScoped... My bad!!