-
Notifications
You must be signed in to change notification settings - Fork 2
Native Source Generation
Classes marked as a native object via IOsuNativeObject<T> will source-generate a native struct representation of the object. Inside these classes, the OsuNativeField and OsuNativeFunction attributes apply.
These structs act as a native representation of the managed object, containing the handle to the managed object (see ManagedObjectHandle<T>) and potentially other fields (see OsuNativeField). They are supposed to be returned to the caller, passing back the handle and optionally additional information.
The name of the struct is determined by the class name, prefixing it with Native and trimming off a potential suffix Object (eg. BeatmapObject -> NativeBeatmap).
Example:
source
public partial class BeatmapObject : IOsuNativeObject<FlatWorkingBeatmap> { }generated
internal unsafe struct NativeBeatmap
{
public required ManagedObjectHandle<global::osu.Native.Objects.FlatWorkingBeatmap> Handle;
}Additionally, a native Destroy function is generated for the object, allowing the caller to destroy the managed object of the handle.
Fields in an IOsuNativeObject marked with the OsuNativeField attribute are included in the source-generated native struct. The field is required to be assigned a value when constructing the native struct object, and intends to pass simple information back to the caller.
Only unmanaged types may be used as types for a native field, otherwise a compiler error is emitted by a source analyzer.
The name of the field is determined by the field name, trimming a potential _ and capitalizing it in pascal case (eg. _beatmapId -> BeatmapId).
Example:
source
public partial class BeatmapObject : IOsuNativeObject<FlatWorkingBeatmap>
{
[OsuNativeField]
private readonly int _beatmapId;
}generated
internal unsafe struct NativeBeatmap
{
public required ManagedObjectHandle<global::osu.Game.Beatmaps.FlatWorkingBeatmap> Handle;
public required int BeatmapId;
}Methods in an IOsuNativeObject marked with the OsuNativeFunction attribute generate a corresponding native function (UnmanagedCallersOnly). The return type of these methods must be an ErrorCode (see Error Handling), and they must be static.
The name of the native functions is determined by the method name and class name, using the object name (parsed the same way as the native structs) as a prefix (eg. BeatmapObject and GetTitle -> Beatmap_GetTitle).
Example:
source
public partial class BeatmapObject : IOsuNativeObject<FlatWorkingBeatmap>
{
[OsuNativeFunction]
private static ErrorCode Foo(ManagedObjectHandle<FlatWorkingBeatmap> beatmapHandle) => ...;
}generated
[UnmanagedCallersOnly(EntryPoint = "Beatmap_Foo", CallConvs = [typeof(CallConvCdecl)])]
private static ErrorCode Beatmap_Foo(global::osu.Native.Objects.ManagedObjectHandle<global::osu.Game.Beatmaps.FlatWorkingBeatmap> beatmapHandle)
{
ErrorHandler.SetLastMessage(null);
try
{
return osu.Native.Objects.BeatmapObject.Foo(beatmapHandle);
}
catch (Exception ex)
{
return ErrorHandler.HandleException(ex);
}
}A ManagedObjectHandle<T> represents a reference to a managed object inside osu-native, and is natively depicted by a signed 32-bit integer:
internal struct ManagedObjectHandle<T>
{
public int Id;
}On structs that are a native representation of a managed object, this handle will always be the first element in the struct layout. This handle is supposed to be stored by the caller to reference the managed object in function calls later on, and is often used in the parameter for it.