Refactor call event
This commit is contained in:
parent
dfbdab7e95
commit
605858e14f
@ -14,7 +14,6 @@ public class NativePatchBukkit {
|
|||||||
|
|
||||||
private static final Linker LINKER = Linker.nativeLinker();
|
private static final Linker LINKER = Linker.nativeLinker();
|
||||||
|
|
||||||
private static MethodHandle callEventNative;
|
|
||||||
private static MethodHandle getLocationNative;
|
private static MethodHandle getLocationNative;
|
||||||
private static MethodHandle getWorldNative;
|
private static MethodHandle getWorldNative;
|
||||||
private static MethodHandle freeStringNative;
|
private static MethodHandle freeStringNative;
|
||||||
@ -181,7 +180,6 @@ public class NativePatchBukkit {
|
|||||||
* Called from Rust during initialization to register native function pointers.
|
* Called from Rust during initialization to register native function pointers.
|
||||||
*/
|
*/
|
||||||
public static void initCallbacks(
|
public static void initCallbacks(
|
||||||
long callEventAddr,
|
|
||||||
long getLocationAddr,
|
long getLocationAddr,
|
||||||
long freeStringAddr,
|
long freeStringAddr,
|
||||||
long getWorldAddr,
|
long getWorldAddr,
|
||||||
@ -189,18 +187,6 @@ public class NativePatchBukkit {
|
|||||||
long playerEntityPlaySoundAddr,
|
long playerEntityPlaySoundAddr,
|
||||||
long playerPlaySoundAddr
|
long playerPlaySoundAddr
|
||||||
) {
|
) {
|
||||||
// bool rust_call_event(const char* event_type, const char* event_data_json)
|
|
||||||
// Returns true if Pumpkin handled it, false if unknown event type
|
|
||||||
callEventNative = LINKER.downcallHandle(
|
|
||||||
MemorySegment.ofAddress(callEventAddr),
|
|
||||||
FunctionDescriptor.of(
|
|
||||||
ValueLayout.JAVA_BOOLEAN, // return: was handled by Pumpkin
|
|
||||||
ValueLayout.ADDRESS, // event_type string
|
|
||||||
ValueLayout.ADDRESS // event_data_json string
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// bool rust_get_location(const char* uuid, Vec3FFI* out)
|
// bool rust_get_location(const char* uuid, Vec3FFI* out)
|
||||||
getLocationNative = LINKER.downcallHandle(
|
getLocationNative = LINKER.downcallHandle(
|
||||||
MemorySegment.ofAddress(getLocationAddr),
|
MemorySegment.ofAddress(getLocationAddr),
|
||||||
@ -426,24 +412,4 @@ public class NativePatchBukkit {
|
|||||||
throw new RuntimeException("Failed to call native playerPlaySound", t);
|
throw new RuntimeException("Failed to call native playerPlaySound", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean callEvent(Event event) {
|
|
||||||
try (Arena arena = Arena.ofConfined()) {
|
|
||||||
// Serialize event data to JSON for Rust
|
|
||||||
String eventDataJson = PatchBukkitEventSerializer.serialize(event);
|
|
||||||
|
|
||||||
MemorySegment eventTypeStr = arena.allocateFrom(event.getEventName());
|
|
||||||
MemorySegment eventDataStr = arena.allocateFrom(eventDataJson);
|
|
||||||
|
|
||||||
boolean handled = (boolean) callEventNative.invokeExact(eventTypeStr, eventDataStr);
|
|
||||||
|
|
||||||
// If Pumpkin handled it and event is Cancellable, we need to read back
|
|
||||||
// the cancelled state. This is handled by Rust calling back to update
|
|
||||||
// the event object directly.
|
|
||||||
|
|
||||||
return handled;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
throw new RuntimeException("Failed to call event: " + event.getEventName(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,8 +80,8 @@ import net.kyori.adventure.text.Component;
|
|||||||
import net.kyori.adventure.util.TriState;
|
import net.kyori.adventure.util.TriState;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
import patchbukkit.bridge.NativeBridgeFfi;
|
import patchbukkit.bridge.NativeBridgeFfi;
|
||||||
import patchbukkit.common.SendMessageRequest;
|
import patchbukkit.message.SendMessageRequest;
|
||||||
import patchbukkit.common.SetAbilitiesRequest;
|
import patchbukkit.abilities.SetAbilitiesRequest;
|
||||||
|
|
||||||
@SuppressWarnings({ "deprecation", "removal" })
|
@SuppressWarnings({ "deprecation", "removal" })
|
||||||
public class PatchBukkitPlayer
|
public class PatchBukkitPlayer
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import org.bukkit.plugin.EventExecutor;
|
|||||||
import org.bukkit.plugin.IllegalPluginAccessException;
|
import org.bukkit.plugin.IllegalPluginAccessException;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.RegisteredListener;
|
import org.bukkit.plugin.RegisteredListener;
|
||||||
|
import org.patchbukkit.bridge.BridgeUtils;
|
||||||
import org.patchbukkit.bridge.NativePatchBukkit;
|
import org.patchbukkit.bridge.NativePatchBukkit;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
@ -20,7 +21,9 @@ import co.aikar.timings.TimedEventExecutor;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import patchbukkit.bridge.NativeBridgeFfi;
|
import patchbukkit.bridge.NativeBridgeFfi;
|
||||||
import patchbukkit.common.RegisterEventRequest;
|
import patchbukkit.events.CallEventRequest;
|
||||||
|
import patchbukkit.events.PlayerJoinEvent;
|
||||||
|
import patchbukkit.events.RegisterEventRequest;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -45,7 +48,22 @@ public class PatchBukkitEventManager {
|
|||||||
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean handledByPumpkin = NativePatchBukkit.callEvent(event);
|
var request = CallEventRequest.newBuilder();
|
||||||
|
switch (event.getEventName()) {
|
||||||
|
case "org.bukkit.event.player.PlayerJoinEvent":
|
||||||
|
var castedEvent = (org.bukkit.event.player.PlayerJoinEvent) event;
|
||||||
|
request.setPlayerJoin(
|
||||||
|
PlayerJoinEvent.newBuilder()
|
||||||
|
.setJoinMessage(castedEvent.joinMessage().toString())
|
||||||
|
.setPlayerUuid(BridgeUtils.convertUuid(castedEvent.getPlayer().getUniqueId()))
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var response = NativeBridgeFfi.callEvent(request.build());
|
||||||
|
|
||||||
|
boolean handledByPumpkin;
|
||||||
|
if (response == null) handledByPumpkin = false;
|
||||||
|
else handledByPumpkin = response.getHandled();
|
||||||
|
|
||||||
if (!handledByPumpkin) {
|
if (!handledByPumpkin) {
|
||||||
// Pumpkin doesn't know this event type, dispatch Java-only
|
// Pumpkin doesn't know this event type, dispatch Java-only
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package patchbukkit.common;
|
package patchbukkit.abilities;
|
||||||
|
|
||||||
import "patchbukkit/common/types.proto";
|
import "patchbukkit/common/types.proto";
|
||||||
|
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_package = "patchbukkit.common";
|
option java_package = "patchbukkit.abilities";
|
||||||
|
|
||||||
message SetAbilitiesRequest {
|
message SetAbilitiesRequest {
|
||||||
patchbukkit.common.UUID uuid = 1;
|
patchbukkit.common.UUID uuid = 1;
|
||||||
patchbukkit.common.Abilities abilities = 2;
|
Abilities abilities = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetAbilitiesResponse {
|
||||||
|
bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Abilities {
|
message Abilities {
|
||||||
17
proto/patchbukkit/bridge.proto
Normal file
17
proto/patchbukkit/bridge.proto
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package patchbukkit.bridge;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
import "patchbukkit/abilities.proto";
|
||||||
|
import "patchbukkit/common/types.proto";
|
||||||
|
import "patchbukkit/events/event.proto";
|
||||||
|
import "patchbukkit/message.proto";
|
||||||
|
|
||||||
|
service NativeBridge {
|
||||||
|
rpc GetAbilities(patchbukkit.common.UUID) returns (patchbukkit.abilities.Abilities);
|
||||||
|
rpc SetAbilities(patchbukkit.abilities.SetAbilitiesRequest) returns (patchbukkit.abilities.SetAbilitiesResponse);
|
||||||
|
rpc RegisterEvent(patchbukkit.events.RegisterEventRequest) returns (google.protobuf.Empty);
|
||||||
|
rpc CallEvent(patchbukkit.events.CallEventRequest) returns (patchbukkit.events.CallEventResponse);
|
||||||
|
rpc SendMessage(patchbukkit.message.SendMessageRequest) returns (google.protobuf.Empty);
|
||||||
|
}
|
||||||
@ -1,16 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
package patchbukkit.bridge;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/wrappers.proto";
|
|
||||||
import "patchbukkit/common/abilities.proto";
|
|
||||||
import "patchbukkit/common/events.proto";
|
|
||||||
import "patchbukkit/common/message.proto";
|
|
||||||
import "patchbukkit/common/types.proto";
|
|
||||||
|
|
||||||
service NativeBridge {
|
|
||||||
rpc GetAbilities(patchbukkit.common.UUID) returns (patchbukkit.common.Abilities);
|
|
||||||
rpc SetAbilities(patchbukkit.common.SetAbilitiesRequest) returns (google.protobuf.BoolValue);
|
|
||||||
rpc RegisterEvent(patchbukkit.common.RegisterEventRequest) returns (google.protobuf.Empty);
|
|
||||||
rpc SendMessage(patchbukkit.common.SendMessageRequest) returns (google.protobuf.Empty);
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package patchbukkit.common;
|
|
||||||
|
|
||||||
import "patchbukkit/common/types.proto";
|
|
||||||
|
|
||||||
option java_multiple_files = true;
|
|
||||||
option java_package = "patchbukkit.common";
|
|
||||||
|
|
||||||
message RegisterEventRequest {
|
|
||||||
string event_type = 1;
|
|
||||||
string plugin_name = 2;
|
|
||||||
int32 priority = 3;
|
|
||||||
bool blocking = 4;
|
|
||||||
}
|
|
||||||
@ -2,16 +2,25 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package patchbukkit.events;
|
package patchbukkit.events;
|
||||||
|
|
||||||
|
import "patchbukkit/common/types.proto";
|
||||||
import "patchbukkit/events/player_events.proto";
|
import "patchbukkit/events/player_events.proto";
|
||||||
|
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_package = "patchbukkit.events";
|
option java_package = "patchbukkit.events";
|
||||||
|
|
||||||
message Event {
|
message RegisterEventRequest {
|
||||||
string event_id = 1;
|
string event_type = 1;
|
||||||
bool cancellable = 2;
|
string plugin_name = 2;
|
||||||
|
int32 priority = 3;
|
||||||
|
bool blocking = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CallEventRequest {
|
||||||
oneof event_data {
|
oneof event_data {
|
||||||
PlayerJoinEvent player_join = 10;
|
PlayerJoinEvent player_join = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CallEventResponse {
|
||||||
|
bool handled = 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,6 @@ option java_multiple_files = true;
|
|||||||
option java_package = "patchbukkit.events";
|
option java_package = "patchbukkit.events";
|
||||||
|
|
||||||
message PlayerJoinEvent {
|
message PlayerJoinEvent {
|
||||||
patchbukkit.common.UUID player = 1;
|
patchbukkit.common.UUID player_uuid = 1;
|
||||||
string join_message = 2;
|
string join_message = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package patchbukkit.common;
|
package patchbukkit.message;
|
||||||
|
|
||||||
import "patchbukkit/common/types.proto";
|
import "patchbukkit/common/types.proto";
|
||||||
|
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_package = "patchbukkit.common";
|
option java_package = "patchbukkit.message";
|
||||||
|
|
||||||
message SendMessageRequest {
|
message SendMessageRequest {
|
||||||
patchbukkit.common.UUID uuid = 1;
|
patchbukkit.common.UUID uuid = 1;
|
||||||
@ -1,5 +1,8 @@
|
|||||||
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
||||||
use crate::proto::patchbukkit::common::{Abilities, SetAbilitiesRequest, Uuid};
|
use crate::proto::patchbukkit::{
|
||||||
|
abilities::{Abilities, SetAbilitiesRequest},
|
||||||
|
common::Uuid,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
|
pub fn ffi_native_bridge_get_abilities_impl(request: Uuid) -> Option<Abilities> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
let ctx = CALLBACK_CONTEXT.get()?;
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::ffi::c_char;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin::plugin::EventPriority;
|
use pumpkin::plugin::EventPriority;
|
||||||
@ -6,8 +5,11 @@ use pumpkin::plugin::player::player_join::PlayerJoinEvent;
|
|||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
use crate::events::handler::PatchBukkitEventHandler;
|
use crate::events::handler::PatchBukkitEventHandler;
|
||||||
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string};
|
use crate::java::native_callbacks::CALLBACK_CONTEXT;
|
||||||
use crate::proto::patchbukkit::common::RegisterEventRequest;
|
use crate::proto::patchbukkit::events::call_event_request::EventData;
|
||||||
|
use crate::proto::patchbukkit::events::{
|
||||||
|
CallEventRequest, CallEventResponse, RegisterEventRequest,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> Option<()> {
|
pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> Option<()> {
|
||||||
let ctx = CALLBACK_CONTEXT.get()?;
|
let ctx = CALLBACK_CONTEXT.get()?;
|
||||||
@ -61,44 +63,32 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O
|
|||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "C" fn rust_call_event(
|
pub fn ffi_native_bridge_call_event_impl(request: CallEventRequest) -> Option<CallEventResponse> {
|
||||||
event_type_ptr: *const c_char,
|
let ctx = CALLBACK_CONTEXT.get()?;
|
||||||
event_data_ptr: *const c_char,
|
let event_data = request.event_data?;
|
||||||
) -> bool {
|
log::debug!("Java calling event {:?}", event_data);
|
||||||
let event_type = get_string(event_type_ptr);
|
|
||||||
let event_data_json = get_string(event_data_ptr);
|
|
||||||
|
|
||||||
let Some(ctx) = CALLBACK_CONTEXT.get() else {
|
|
||||||
log::error!("CallbackContext not initialized when calling event");
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!("Java calling event '{event_type}' with data: {event_data_json}");
|
|
||||||
|
|
||||||
let event_data: serde_json::Value = match serde_json::from_str(&event_data_json) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to parse event data JSON: {e}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let context = ctx.plugin_context.clone();
|
let context = ctx.plugin_context.clone();
|
||||||
|
|
||||||
tokio::task::block_in_place(|| {
|
tokio::task::block_in_place(|| {
|
||||||
ctx.runtime.block_on(async {
|
ctx.runtime.block_on(async {
|
||||||
if event_type.as_str() == "org.bukkit.event.player.PlayerJoinEvent" {
|
match event_data {
|
||||||
let player_uuid_str = event_data["playerUuid"].as_str().unwrap_or("");
|
EventData::PlayerJoin(player_join_event_data) => {
|
||||||
let join_message_str = event_data["joinMessage"].as_str().unwrap_or("");
|
if let Ok(uuid) =
|
||||||
if let Ok(uuid) = uuid::Uuid::parse_str(player_uuid_str)
|
uuid::Uuid::parse_str(&player_join_event_data.player_uuid?.value)
|
||||||
&& let Some(player) = context.server.get_player_by_uuid(uuid)
|
|
||||||
{
|
{
|
||||||
|
if let Some(player) = context.server.get_player_by_uuid(uuid) {
|
||||||
let pumpkin_event = PlayerJoinEvent::new(
|
let pumpkin_event = PlayerJoinEvent::new(
|
||||||
player,
|
player,
|
||||||
TextComponent::from_legacy_string(join_message_str),
|
TextComponent::from_legacy_string(
|
||||||
|
&player_join_event_data.join_message,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
context.server.plugin_manager.fire(pumpkin_event).await;
|
context.server.plugin_manager.fire(pumpkin_event).await;
|
||||||
return true;
|
return Some(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(false)
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
@ -106,5 +96,9 @@ pub extern "C" fn rust_call_event(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
|
|
||||||
|
let mut response = CallEventResponse::default();
|
||||||
|
response.handled = handled?;
|
||||||
|
Some(response)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
use std::ffi::c_char;
|
|
||||||
|
|
||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string},
|
java::native_callbacks::CALLBACK_CONTEXT, proto::patchbukkit::message::SendMessageRequest,
|
||||||
proto::patchbukkit::common::SendMessageRequest,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn ffi_native_bridge_send_message_impl(request: SendMessageRequest) -> Option<()> {
|
pub fn ffi_native_bridge_send_message_impl(request: SendMessageRequest) -> Option<()> {
|
||||||
|
|||||||
@ -79,7 +79,6 @@ pub extern "C" fn rust_free_bytes(ptr: *mut u8, len: u32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
||||||
let call_event_addr = events::rust_call_event as *const () as i64;
|
|
||||||
let get_location_addr = location::rust_get_location as *const () as i64;
|
let get_location_addr = location::rust_get_location as *const () as i64;
|
||||||
let free_string_addr = memory::rust_free_string as *const () as i64;
|
let free_string_addr = memory::rust_free_string as *const () as i64;
|
||||||
let get_world_addr = world::rust_get_world as *const () as i64;
|
let get_world_addr = world::rust_get_world as *const () as i64;
|
||||||
@ -92,7 +91,6 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
|
|||||||
"org.patchbukkit.bridge.NativePatchBukkit",
|
"org.patchbukkit.bridge.NativePatchBukkit",
|
||||||
"initCallbacks",
|
"initCallbacks",
|
||||||
&[
|
&[
|
||||||
InvocationArg::try_from(call_event_addr)?.into_primitive()?,
|
|
||||||
InvocationArg::try_from(get_location_addr)?.into_primitive()?,
|
InvocationArg::try_from(get_location_addr)?.into_primitive()?,
|
||||||
InvocationArg::try_from(free_string_addr)?.into_primitive()?,
|
InvocationArg::try_from(free_string_addr)?.into_primitive()?,
|
||||||
InvocationArg::try_from(get_world_addr)?.into_primitive()?,
|
InvocationArg::try_from(get_world_addr)?.into_primitive()?,
|
||||||
|
|||||||
@ -10,6 +10,14 @@ pub mod patchbukkit {
|
|||||||
pub mod events {
|
pub mod events {
|
||||||
include!(concat!(env!("OUT_DIR"), "/patchbukkit.events.rs"));
|
include!(concat!(env!("OUT_DIR"), "/patchbukkit.events.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod abilities {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/patchbukkit.abilities.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod message {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/patchbukkit.message.rs"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/ffi_init.rs"));
|
include!(concat!(env!("OUT_DIR"), "/ffi_init.rs"));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user