Raylib structs are mapped to shadow classes (Rectangle, Vector, Image, etc.) or wrapper and utility classes (Draw, Window, etc.). Due to dart:ffi limitations, all Raylib data struct implementations extend NativeWrapper<T extends NativeType>, which manages allocation and deallocation of the native memory pointer.
class NativeWrapper<T extends NativeType> {
/// The raw physical address pointer pointing to the native heap memory.
final Pointer<T> pointer;
/// The number of sequential elements of type [T] allocated in memory (useful for arrays).
final int length;
[...]
}All classes that use NativeResource<T> and inherit from Disposable implement a Free() method. This function calls the respective native Unload functions, detaches the instance from the Dart Garbage Collector, and frees the instance pointer.
@override
void Free() {
if (_memory != null && !_memory!.isDisposed) {
_finalizer.detach(this);
_unloadTexture(ref);
super.Free(); // Calls in function free()
}
} All NativeWrapper inheritance classes implement a static final _finalizer which adds the instance to Dart Garbage Collector, calling malloc.free() on the pointer. However, due to the umpredictability of the Garbage Collector, a helper class was implemented, the RayArena
class RaylibArena {
final List<NativeWrapper> _registry;
RaylibArena([List<NativeWrapper>? instances]) : _registry = instances ?? [];
[...]
}RayArena implements three methods:
- register/registerAll: take the input, adds to the registry and, for
register, returns. registerAll can be used as a deconstructor to be called on global variables atmainbefore closing the program. - release: calls
Free()on all instances in theregistrylist. - run: simply calls the anonymous function passed.
To avoid creating a new variable for every use of RayArena, its recomended the use of this another method, the rayCompute that mirrors the ffi package.
T rayCompute<T extends NativeWrapper?>(T Function(RaylibArena arena) rayArena) {
final arena = RaylibArena();
final result = rayArena.call(arena);
try {
return result;
}
finally {
arena.release();
}
}prop.Draw(); // Calling Draw method assigned to for its Model member
// Drawing the BoundingBox cube wires. rayCompute.
// It returns the result inside the anonymous function, however in the operator
// subtraction, we use [register] method to add it to the arena
rayCompute((rayArena) {
var size = rayArena.register(prop.boundingBox.max - prop.boundingBox.min);
Shapes3D.DrawCubeWiresV(prop.position, size, .WHITE);
});Developer Note: Most constructors define an optional RayArena parameter to automatically add it to the arena. In cases were is isn't viable we call rayArena.register on it manually.
In Dart, extends creates an inheritance relationship, while implements defines a obrigatory method.
When composing classes that contain Raylib types, its important to add Disposeable as the class inheritance and override the Free() to free these members.
class Prop implements Disposeable {
/// Prop respective model
final Model model;
/// Prop user defined collision box
late final BoundingBox boundingBox;
/// Prop transform for drawing and collision computations
late final Transform transform;
[...]
@override
void Free() {
model.Free();
transform.Free();
position.Free();
}
}
To maximize performance, these utility classes were created to syncronize a C string pointer to it's string twin member.
class Text implements Disposeable
{
StringBuffer _buffer = StringBuffer();
late Pointer<Uint8> _array;
[...]
Pointer<Utf8> get ref
{
if (_isDirt) {
final units = utf8.encode(_buffer.toString());
_EnsureCapacity(units.length);
_array.asTypedList(_length).setAll(0, units);
_array[units.length] = 0;
_isDirt = false;
}
return _array.cast<Utf8>();
}
}- Lazy Syncronization: The array pointer only expands its memory when
refgetter is called. - Memory Safety:
DisposeableandFinalizerare integrated to give the user a safety nest ifdispose()isn't called. - Exponential Allocation: The array is automatically resized by doubling it's capacity. Achieving more efficiency for big texts.