use super::mount::{hasVisualStyle, sizeFlags};
use super::*;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;
use std::sync::{Mutex, OnceLock};
#[derive(Clone)]
pub(crate) struct GodotBridge {
pub(crate) stringNameNewWithUtf8Chars: GDExtensionInterfaceStringNameNewWithUtf8Chars,
pub(crate) stringNewWithUtf8Chars: GDExtensionInterfaceStringNewWithUtf8Chars,
pub(crate) stringToUtf8Chars: GDExtensionInterfaceStringToUtf8Chars,
pub(crate) classdbGetMethodBind: GDExtensionInterfaceClassdbGetMethodBind,
pub(crate) objectMethodBindPtrcall: GDExtensionInterfaceObjectMethodBindPtrcall,
pub(crate) classdbConstructObject: GDExtensionInterfaceClassdbConstructObject,
pub(crate) globalGetSingleton: GDExtensionInterfaceGlobalGetSingleton,
pub(crate) variantNewNil: GDExtensionInterfaceVariantNewNil,
pub(crate) variantDestroy: GDExtensionInterfaceVariantDestroy,
pub(crate) variantFromBool: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) variantFromInt: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) variantFromFloat: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) variantFromString: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) variantFromVector2: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) variantFromColor: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) variantFromObject: GDExtensionVariantFromTypeConstructorFunc,
pub(crate) objectMethodBindCall: GDExtensionInterfaceObjectMethodBindCall,
pub(crate) stringDestructor: GDExtensionPtrDestructor,
pub(crate) stringNameDestructor: GDExtensionPtrDestructor,
pub(crate) methods: GodotMethods,
}
impl GodotBridge {
pub(crate) unsafe fn load(
getProcAddress: GDExtensionInterfaceGetProcAddress,
_library: GDExtensionClassLibraryPtr,
) -> Option<Self> {
let stringNameNewWithUtf8Chars =
unsafe { loadProc(getProcAddress, b"string_name_new_with_utf8_chars\0")? };
let stringNewWithUtf8Chars =
unsafe { loadProc(getProcAddress, b"string_new_with_utf8_chars\0")? };
let stringToUtf8Chars = unsafe { loadProc(getProcAddress, b"string_to_utf8_chars\0")? };
let classdbGetMethodBind =
unsafe { loadProc(getProcAddress, b"classdb_get_method_bind\0")? };
let objectMethodBindPtrcall =
unsafe { loadProc(getProcAddress, b"object_method_bind_ptrcall\0")? };
let classdbConstructObject = unsafe {
loadProc(getProcAddress, b"classdb_construct_object3\0")
.or_else(|| loadProc(getProcAddress, b"classdb_construct_object2\0"))
.or_else(|| loadProc(getProcAddress, b"classdb_construct_object\0"))?
};
let globalGetSingleton = unsafe { loadProc(getProcAddress, b"global_get_singleton\0")? };
let variantNewNil = unsafe { loadProc(getProcAddress, b"variant_new_nil\0")? };
let variantDestroy = unsafe { loadProc(getProcAddress, b"variant_destroy\0")? };
let getVariantFromTypeConstructor = unsafe {
loadProc::<GDExtensionInterfaceGetVariantFromTypeConstructor>(
getProcAddress,
b"get_variant_from_type_constructor\0",
)?
};
let objectMethodBindCall =
unsafe { loadProc(getProcAddress, b"object_method_bind_call\0")? };
let variantGetPtrDestructor = unsafe {
loadProc::<GDExtensionInterfaceVariantGetPtrDestructor>(
getProcAddress,
b"variant_get_ptr_destructor\0",
)?
};
let stringDestructor = unsafe { variantGetPtrDestructor(VARIANT_STRING)? };
let stringNameDestructor = unsafe { variantGetPtrDestructor(VARIANT_STRING_NAME)? };
let variantFromBool = unsafe { getVariantFromTypeConstructor(VARIANT_BOOL)? };
let variantFromInt = unsafe { getVariantFromTypeConstructor(VARIANT_INT)? };
let variantFromFloat = unsafe { getVariantFromTypeConstructor(VARIANT_FLOAT)? };
let variantFromString = unsafe { getVariantFromTypeConstructor(VARIANT_STRING)? };
let variantFromVector2 = unsafe { getVariantFromTypeConstructor(VARIANT_VECTOR2)? };
let variantFromColor = unsafe { getVariantFromTypeConstructor(VARIANT_COLOR)? };
let variantFromObject = unsafe { getVariantFromTypeConstructor(VARIANT_OBJECT)? };
Some(Self {
stringNameNewWithUtf8Chars,
stringNewWithUtf8Chars,
stringToUtf8Chars,
classdbGetMethodBind,
objectMethodBindPtrcall,
classdbConstructObject,
globalGetSingleton,
variantNewNil,
variantDestroy,
variantFromBool,
variantFromInt,
variantFromFloat,
variantFromString,
variantFromVector2,
variantFromColor,
variantFromObject,
objectMethodBindCall,
stringDestructor,
stringNameDestructor,
methods: GodotMethods::empty(),
})
}
}
impl GodotBridge {
pub(crate) fn construct(&self, class: &str) -> GodoruResult<GodotObject> {
let className = self.stringName(class)?;
let object = unsafe { (self.classdbConstructObject)(className.as_ptr()) };
if object.is_null() {
return Err(GodoruError::GodotObjectCreate(class.to_string()));
}
let object = GodotObject(object);
if matches!(
class,
"Control"
| "PanelContainer"
| "ColorRect"
| "VBoxContainer"
| "HBoxContainer"
| "VFlowContainer"
| "HFlowContainer"
| "Label"
| "TextureRect"
| "Button"
| "LineEdit"
| "CheckBox"
| "ScrollContainer"
) {
self.notification(object, NOTIFICATION_POSTINITIALIZE, false)?;
}
Ok(object)
}
pub(crate) fn singleton(&self, name: &str) -> GodoruResult<GodotObject> {
let name = self.stringName(name)?;
let object = unsafe { (self.globalGetSingleton)(name.as_ptr()) };
if object.is_null() {
Err(GodoruError::GodotRuntime(
"Godot singleton returned null".to_string(),
))
} else {
Ok(GodotObject(object))
}
}
pub(crate) fn methodBind(
&self,
class: &str,
method: &str,
hash: i64,
) -> GodoruResult<GDExtensionMethodBindPtr> {
let className = self.stringName(class)?;
let methodName = self.stringName(method)?;
let methodBind =
unsafe { (self.classdbGetMethodBind)(className.as_ptr(), methodName.as_ptr(), hash) };
if methodBind.is_null() {
Err(GodoruError::GodotMethodUnavailable(format!(
"{class}.{method}"
)))
} else {
Ok(methodBind)
}
}
pub(crate) fn callVoid(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
args: &[GDExtensionConstTypePtr],
) -> GodoruResult<()> {
if object.0.is_null() {
return Err(GodoruError::GodotRuntime(
"method receiver is null".to_string(),
));
}
unsafe {
(self.objectMethodBindPtrcall)(methodBind, object.0, args.as_ptr(), ptr::null_mut());
}
Ok(())
}
pub(crate) fn callObject(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
args: &[GDExtensionConstTypePtr],
) -> GodoruResult<GodotObject> {
if object.0.is_null() {
return Err(GodoruError::GodotRuntime(
"method receiver is null".to_string(),
));
}
let mut result = ptr::null_mut();
unsafe {
(self.objectMethodBindPtrcall)(
methodBind,
object.0,
args.as_ptr(),
&mut result as *mut GDExtensionObjectPtr as GDExtensionTypePtr,
);
}
Ok(GodotObject(result))
}
pub(crate) fn callBool(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
args: &[GDExtensionConstTypePtr],
) -> GodoruResult<bool> {
if object.0.is_null() {
return Err(GodoruError::GodotRuntime(
"method receiver is null".to_string(),
));
}
let mut result = 0u8;
unsafe {
(self.objectMethodBindPtrcall)(
methodBind,
object.0,
args.as_ptr(),
&mut result as *mut u8 as GDExtensionTypePtr,
);
}
Ok(result != 0)
}
pub(crate) fn callString(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
args: &[GDExtensionConstTypePtr],
) -> GodoruResult<String> {
if object.0.is_null() {
return Err(GodoruError::GodotRuntime(
"method receiver is null".to_string(),
));
}
let mut result = GodotString::new(self.stringDestructor);
unsafe {
(self.objectMethodBindPtrcall)(
methodBind,
object.0,
args.as_ptr(),
result.as_mut_ptr(),
);
}
let len = unsafe { (self.stringToUtf8Chars)(result.as_ptr(), ptr::null_mut(), 0) };
if len <= 0 {
return Ok(String::new());
}
let mut buffer = vec![0u8; len as usize];
unsafe {
(self.stringToUtf8Chars)(result.as_ptr(), buffer.as_mut_ptr() as *mut c_char, len);
}
if buffer.last() == Some(&0) {
buffer.pop();
}
Ok(String::from_utf8_lossy(&buffer).into_owned())
}
pub(crate) fn notification(
&self,
object: GodotObject,
what: i64,
reversed: bool,
) -> GodoruResult<()> {
let reversed = boolArg(reversed);
let args = [
&what as *const i64 as GDExtensionConstTypePtr,
&reversed as *const u8 as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.objectNotification, object, &args)
}
pub(crate) fn setName(&self, object: GodotObject, name: &str) -> GodoruResult<()> {
let name = self.stringName(name)?;
let args = [name.as_ptr()];
self.callVoid(self.methods.nodeSetName, object, &args)
}
pub(crate) fn addChild(&self, parent: GodotObject, child: GodotObject) -> GodoruResult<()> {
let forceReadableName = boolArg(false);
let internal = INTERNAL_MODE_DISABLED;
let args = [
&child.0 as *const GDExtensionObjectPtr as GDExtensionConstTypePtr,
&forceReadableName as *const u8 as GDExtensionConstTypePtr,
&internal as *const i64 as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.nodeAddChild, parent, &args)
}
pub(crate) fn queueFree(&self, object: GodotObject) -> GodoruResult<()> {
self.callVoid(self.methods.nodeQueueFree, object, &[])
}
pub(crate) fn fullRect(&self, object: GodotObject) -> GodoruResult<()> {
let preset = LAYOUT_PRESET_FULL_RECT;
let keepOffsets = boolArg(false);
let args = [
&preset as *const i64 as GDExtensionConstTypePtr,
&keepOffsets as *const u8 as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.controlSetAnchorsPreset, object, &args)
}
pub(crate) fn setOffsets(&self, object: GodotObject, spacing: &Spacing) -> GodoruResult<()> {
self.setOffset(object, SIDE_LEFT, spacing.left as f64)?;
self.setOffset(object, SIDE_TOP, spacing.top as f64)?;
self.setOffset(object, SIDE_RIGHT, -(spacing.right as f64))?;
self.setOffset(object, SIDE_BOTTOM, -(spacing.bottom as f64))
}
pub(crate) fn setOffset(
&self,
object: GodotObject,
side: i64,
offset: f64,
) -> GodoruResult<()> {
let args = [
&side as *const i64 as GDExtensionConstTypePtr,
&offset as *const f64 as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.controlSetOffset, object, &args)
}
pub(crate) fn applySize<A>(
&self,
object: GodotObject,
container: &Container<A>,
) -> GodoruResult<()> {
let mut width = container.customMinimumSize.map(|size| size.0).unwrap_or(0);
let mut height = container.customMinimumSize.map(|size| size.1).unwrap_or(0);
if let SizeMode::Fixed(value) = container.width {
width = value;
}
if let SizeMode::Fixed(value) = container.height {
height = value;
}
if width > 0 || height > 0 {
let size = GodotVector2 {
x: width as f32,
y: height as f32,
};
let args = [&size as *const GodotVector2 as GDExtensionConstTypePtr];
self.callVoid(self.methods.controlSetCustomMinimumSize, object, &args)?;
}
self.setSizeFlags(object, true, sizeFlags(&container.width))?;
self.setSizeFlags(object, false, sizeFlags(&container.height))
}
pub(crate) fn setSizeFlags(
&self,
object: GodotObject,
horizontal: bool,
flags: i64,
) -> GodoruResult<()> {
let args = [&flags as *const i64 as GDExtensionConstTypePtr];
if horizontal {
self.callVoid(self.methods.controlSetHSizeFlags, object, &args)
} else {
self.callVoid(self.methods.controlSetVSizeFlags, object, &args)
}
}
pub(crate) fn addThemeConstantOverride(
&self,
object: GodotObject,
name: &str,
value: i64,
) -> GodoruResult<()> {
let name = self.stringName(name)?;
let args = [
name.as_ptr(),
&value as *const i64 as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.controlAddThemeConstantOverride, object, &args)
}
pub(crate) fn addThemeFontSizeOverride(
&self,
object: GodotObject,
name: &str,
value: i64,
) -> GodoruResult<()> {
let name = self.stringName(name)?;
let args = [
name.as_ptr(),
&value as *const i64 as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.controlAddThemeFontSizeOverride, object, &args)
}
pub(crate) fn addThemeColorOverride(
&self,
object: GodotObject,
name: &str,
color: &Color,
) -> GodoruResult<()> {
let name = self.stringName(name)?;
let color = GodotColor::from(color);
let args = [
name.as_ptr(),
&color as *const GodotColor as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.controlAddThemeColorOverride, object, &args)
}
pub(crate) fn addThemeStyleboxOverride(
&self,
object: GodotObject,
name: &str,
styleBox: GodotObject,
) -> GodoruResult<()> {
let name = self.stringName(name)?;
let args = [
name.as_ptr(),
&styleBox.0 as *const GDExtensionObjectPtr as GDExtensionConstTypePtr,
];
self.callVoid(self.methods.controlAddThemeStyleboxOverride, object, &args)
}
pub(crate) fn setProperty(
&self,
object: GodotObject,
name: &str,
value: &GodotVariant,
) -> GodoruResult<()> {
let name = self.stringName(name)?;
let args = [name.as_ptr(), value.as_ptr()];
self.callVoid(self.methods.objectSet, object, &args)
}
pub(crate) fn colorRectSetColor(&self, object: GodotObject, color: &Color) -> GodoruResult<()> {
let color = GodotColor::from(color);
let args = [&color as *const GodotColor as GDExtensionConstTypePtr];
self.callVoid(self.methods.colorRectSetColor, object, &args)
}
pub(crate) fn labelSetText(&self, object: GodotObject, value: &str) -> GodoruResult<()> {
let value = self.string(value)?;
let args = [value.as_ptr()];
self.callVoid(self.methods.labelSetText, object, &args)
}
pub(crate) fn buttonSetText(&self, object: GodotObject, value: &str) -> GodoruResult<()> {
let value = self.string(value)?;
let args = [value.as_ptr()];
self.callVoid(self.methods.buttonSetText, object, &args)
}
pub(crate) fn lineEditSetText(&self, object: GodotObject, value: &str) -> GodoruResult<()> {
let value = self.string(value)?;
let args = [value.as_ptr()];
self.callVoid(self.methods.lineEditSetText, object, &args)
}
pub(crate) fn lineEditSetPlaceholder(
&self,
object: GodotObject,
value: &str,
) -> GodoruResult<()> {
let value = self.string(value)?;
let args = [value.as_ptr()];
self.callVoid(self.methods.lineEditSetPlaceholder, object, &args)
}
pub(crate) fn lineEditGetText(&self, object: GodotObject) -> GodoruResult<String> {
self.callString(self.methods.lineEditGetText, object, &[])
}
pub(crate) fn scrollMode(&self, object: GodotObject) -> GodoruResult<()> {
let mode = SCROLL_MODE_AUTO;
let args = [&mode as *const i64 as GDExtensionConstTypePtr];
self.callVoid(
self.methods.scrollContainerSetHorizontalScrollMode,
object,
&args,
)?;
self.callVoid(
self.methods.scrollContainerSetVerticalScrollMode,
object,
&args,
)
}
pub(crate) fn setTextureRectFit(
&self,
object: GodotObject,
fit: &ImageFit,
) -> GodoruResult<()> {
let expand = TEXTURE_RECT_EXPAND_IGNORE_SIZE;
let stretch = match fit {
ImageFit::Fill => TEXTURE_RECT_STRETCH_SCALE,
ImageFit::Cover => TEXTURE_RECT_STRETCH_KEEP_ASPECT_COVERED,
ImageFit::Contain | ImageFit::ScaleDown => TEXTURE_RECT_STRETCH_KEEP_ASPECT_CENTERED,
};
let args = [&expand as *const i64 as GDExtensionConstTypePtr];
self.callVoid(self.methods.textureRectSetExpandMode, object, &args)?;
let args = [&stretch as *const i64 as GDExtensionConstTypePtr];
self.callVoid(self.methods.textureRectSetStretchMode, object, &args)
}
pub(crate) fn setCustomMinimumSize(
&self,
object: GodotObject,
width: u32,
height: u32,
) -> GodoruResult<()> {
let size = GodotVector2 {
x: width as f32,
y: height as f32,
};
let args = [&size as *const GodotVector2 as GDExtensionConstTypePtr];
self.callVoid(self.methods.controlSetCustomMinimumSize, object, &args)
}
pub(crate) fn loadResourceVariant(
&self,
path: &str,
typeHint: &str,
) -> GodoruResult<GodotVariant> {
let loader = self.singleton("ResourceLoader")?;
let path = self.variantString(path)?;
let typeHint = self.variantString(typeHint)?;
let cacheMode = self.variantInt(RESOURCE_LOADER_CACHE_MODE_REUSE);
let args = [path.as_ptr(), typeHint.as_ptr(), cacheMode.as_ptr()];
self.callVariant(self.methods.resourceLoaderLoad, loader, &args)
}
pub(crate) fn loadImageTextureVariant(
&self,
path: &str,
resources: &mut Vec<GodotObject>,
) -> GodoruResult<GodotVariant> {
let image = self.construct("Image")?;
let path = self.variantString(path)?;
let args = [path.as_ptr()];
let _ = self.callVariant(self.methods.imageLoad, image, &args)?;
let texture = self.construct("ImageTexture")?;
let imageVariant = self.variantObject(image);
let args = [imageVariant.as_ptr()];
let _ = self.callVariant(self.methods.imageTextureSetImage, texture, &args)?;
resources.push(image);
resources.push(texture);
Ok(self.variantObject(texture))
}
pub(crate) fn applyControlStyle(
&self,
object: GodotObject,
style: &Style,
slot: &str,
resources: &mut Vec<GodotObject>,
) -> GodoruResult<()> {
if !hasVisualStyle(style) {
return Ok(());
}
let styleBox = self.construct("StyleBoxFlat")?;
if let Some(background) = style.background.as_ref() {
self.styleBoxColor(self.methods.styleBoxFlatSetBgColor, styleBox, background)?;
}
if let Some(radius) = style.radius {
self.styleBoxInt(
self.methods.styleBoxFlatSetCornerRadiusAll,
styleBox,
radius as i64,
)?;
}
if let Some(border) = style.border.as_ref() {
self.styleBoxColor(
self.methods.styleBoxFlatSetBorderColor,
styleBox,
&border.color,
)?;
self.styleBoxInt(
self.methods.styleBoxFlatSetBorderWidthAll,
styleBox,
border.width as i64,
)?;
}
if let Some(shadow) = style.shadow.as_ref() {
self.styleBoxColor(
self.methods.styleBoxFlatSetShadowColor,
styleBox,
&shadow.color,
)?;
self.styleBoxInt(
self.methods.styleBoxFlatSetShadowSize,
styleBox,
shadow.size as i64,
)?;
let offset = GodotVector2 {
x: shadow.offsetX,
y: shadow.offsetY,
};
let args = [&offset as *const GodotVector2 as GDExtensionConstTypePtr];
self.callVoid(self.methods.styleBoxFlatSetShadowOffset, styleBox, &args)?;
}
self.addThemeStyleboxOverride(object, slot, styleBox)?;
resources.push(styleBox);
Ok(())
}
pub(crate) fn applyButtonStyle(
&self,
object: GodotObject,
theme: &AppTheme,
class: Option<&str>,
local: &Style,
resources: &mut Vec<GodotObject>,
) -> GodoruResult<()> {
for (state, slot) in [
(VisualState::Normal, "normal"),
(VisualState::Hover, "hover"),
(VisualState::Pressed, "pressed"),
(VisualState::Focus, "focus"),
] {
let mut style = theme.styleForState(Component::Button, class, state);
crate::ui::mergeStyle(&mut style, local);
self.applyControlStyle(object, &style, slot, resources)?;
}
Ok(())
}
pub(crate) fn applyTextStyle(
&self,
object: GodotObject,
style: &Style,
resources: &mut Vec<GodotObject>,
) -> GodoruResult<()> {
if let Some(color) = style.color.as_ref() {
self.addThemeColorOverride(object, "font_color", color)?;
}
if let Some(size) = style.fontSize {
self.addThemeFontSizeOverride(object, "font_size", size as i64)?;
}
if let Some(font) = style.font.as_ref() {
let font = self.loadResourceVariant(&font.runtimePath(), "Font")?;
self.setProperty(object, "theme_override_fonts/font", &font)?;
}
let _ = resources;
Ok(())
}
pub(crate) fn applyShader(
&self,
object: GodotObject,
shader: Option<&Shader>,
resources: &mut Vec<GodotObject>,
) -> GodoruResult<()> {
let Some(shader) = shader else {
return Ok(());
};
let shaderVariant = match &shader.source {
ShaderSource::File(path) => {
self.loadResourceVariant(&crate::assets::runtimePath(path), "Shader")?
}
ShaderSource::Code(code) => {
let shaderObject = self.construct("Shader")?;
self.shaderSetCode(shaderObject, code)?;
resources.push(shaderObject);
self.variantObject(shaderObject)
}
};
let materialObject = self.construct("ShaderMaterial")?;
self.setProperty(materialObject, "shader", &shaderVariant)?;
for param in &shader.params {
let value = self.shaderValueVariant(¶m.value, resources)?;
self.setProperty(
materialObject,
&format!("shader_parameter/{}", param.name),
&value,
)?;
}
let material = self.variantObject(materialObject);
self.setProperty(object, "material", &material)?;
resources.push(materialObject);
Ok(())
}
pub(crate) fn shaderSetCode(&self, object: GodotObject, code: &str) -> GodoruResult<()> {
let code = self.string(code)?;
let args = [code.as_ptr()];
self.callVoid(self.methods.shaderSetCode, object, &args)
}
pub(crate) fn styleBoxColor(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
color: &Color,
) -> GodoruResult<()> {
let color = GodotColor::from(color);
let args = [&color as *const GodotColor as GDExtensionConstTypePtr];
self.callVoid(methodBind, object, &args)
}
pub(crate) fn styleBoxInt(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
value: i64,
) -> GodoruResult<()> {
let args = [&value as *const i64 as GDExtensionConstTypePtr];
self.callVoid(methodBind, object, &args)
}
pub(crate) fn shaderValueVariant(
&self,
value: &ShaderValue,
resources: &mut Vec<GodotObject>,
) -> GodoruResult<GodotVariant> {
Ok(match value {
ShaderValue::Bool(value) => self.variantBool(*value),
ShaderValue::Int(value) => self.variantInt(*value),
ShaderValue::Float(value) => self.variantFloat(*value),
ShaderValue::Vector2(x, y) => self.variantVector2(*x, *y),
ShaderValue::Color(value) => self.variantColor(value),
ShaderValue::Texture(value) => {
self.loadImageTextureVariant(&value.runtimePath(), resources)?
}
})
}
pub(crate) fn callVariant(
&self,
methodBind: GDExtensionMethodBindPtr,
object: GodotObject,
args: &[GDExtensionConstTypePtr],
) -> GodoruResult<GodotVariant> {
let mut result = GodotVariant::nil(self.variantNewNil, self.variantDestroy);
let mut error = GDExtensionCallError {
error: 0,
_argument: 0,
_expected: 0,
};
unsafe {
(self.objectMethodBindCall)(
methodBind,
object.0,
args.as_ptr(),
args.len() as i64,
result.as_mut_ptr(),
&mut error,
);
}
if error.error == 0 {
Ok(result)
} else {
Err(GodoruError::GodotRuntime(format!(
"Godot method call failed with error {}",
error.error
)))
}
}
pub(crate) fn variantBool(&self, value: bool) -> GodotVariant {
let value = boolArg(value);
GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromBool,
&value as *const u8 as GDExtensionTypePtr,
)
}
pub(crate) fn variantInt(&self, value: i64) -> GodotVariant {
GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromInt,
&value as *const i64 as GDExtensionTypePtr,
)
}
pub(crate) fn variantFloat(&self, value: f64) -> GodotVariant {
GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromFloat,
&value as *const f64 as GDExtensionTypePtr,
)
}
pub(crate) fn variantVector2(&self, x: f32, y: f32) -> GodotVariant {
let value = GodotVector2 { x, y };
GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromVector2,
&value as *const GodotVector2 as GDExtensionTypePtr,
)
}
pub(crate) fn variantString(&self, value: &str) -> GodoruResult<GodotVariant> {
let value = self.string(value)?;
Ok(GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromString,
value.as_ptr() as GDExtensionTypePtr,
))
}
pub(crate) fn variantColor(&self, color: &Color) -> GodotVariant {
let color = GodotColor::from(color);
GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromColor,
&color as *const GodotColor as GDExtensionTypePtr,
)
}
pub(crate) fn variantObject(&self, object: GodotObject) -> GodotVariant {
GodotVariant::fromType(
self.variantNewNil,
self.variantDestroy,
self.variantFromObject,
&object.0 as *const GDExtensionObjectPtr as GDExtensionTypePtr,
)
}
pub(crate) fn stringName(&self, value: &str) -> GodoruResult<StringNameStorage> {
let value = CString::new(value).map_err(|_| GodoruError::InvalidCString(value.into()))?;
let mut storage = StringNameStorage::new(self.stringNameDestructor);
unsafe {
(self.stringNameNewWithUtf8Chars)(storage.as_mut_ptr(), value.as_ptr());
}
Ok(storage)
}
pub(crate) fn string(&self, value: &str) -> GodoruResult<GodotString> {
let value = CString::new(value).map_err(|_| GodoruError::InvalidCString(value.into()))?;
let mut storage = GodotString::new(self.stringDestructor);
unsafe {
(self.stringNewWithUtf8Chars)(storage.as_mut_ptr(), value.as_ptr());
}
Ok(storage)
}
}
pub(crate) fn storeBridge(
getProcAddress: GDExtensionInterfaceGetProcAddress,
library: GDExtensionClassLibraryPtr,
) {
if let Some(bridge) = unsafe { GodotBridge::load(getProcAddress, library) } {
let global = GODOT_BRIDGE.get_or_init(|| Mutex::new(None));
if let Ok(mut current) = global.lock() {
*current = Some(bridge);
}
}
}
pub(crate) fn bridge() -> GodoruResult<GodotBridge> {
GODOT_BRIDGE
.get()
.and_then(|bridge| bridge.lock().ok())
.and_then(|bridge| bridge.clone())
.ok_or(GodoruError::GodotBridgeUnavailable)
}
unsafe impl Send for GodotBridge {}
unsafe impl Sync for GodotBridge {}
unsafe impl Send for GodotMethods {}
unsafe impl Sync for GodotMethods {}
static GODOT_BRIDGE: OnceLock<Mutex<Option<GodotBridge>>> = OnceLock::new();