use std::collections::HashMap;
use std::num::NonZeroU32;
use std::os::raw::c_void;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock, RwLock};
use glow::HasContext;
use jcall::JArg;
use jni::objects::{JByteArray, JClass, JFloatArray, JString};
use jni::sys::{jdouble, jfloat, jint, jstring};
use jni::{JNIEnv, JavaVM};
use libloading::Symbol;
use yog_hot_reload::{HotReloader, LoadedModule, ModuleGeneration, ModuleRegistry};
use yog_abi::{
ABI_VERSION, YogAdvancementEvent, YogAdvancementFn, YogApi, YogAttackEntityFn, YogBlockBreakFn,
YogBlockDef, YogBlockPos, YogChatFn, YogClientFn, YogCommandFn, YogContainerCloseEvent,
YogContainerCloseFn, YogContainerOpenEvent, YogContainerOpenFn, YogCraftEvent, YogCraftFn,
YogEntityDamageFn, YogEntityDeathFn, YogEntityInteractEvent, YogEntityInteractFn,
YogEntitySpawnFn, YogExplosionEvent, YogExplosionFn, YogGfxApi, YogHudRenderFn, YogItemDef,
YogItemPickupEvent, YogItemPickupFn, YogKeyPressEvent, YogKeyPressFn, YogOwnedStr, YogPacketFn,
YogPlaceBlockEvent, YogPlaceBlockFn, YogPlayerDeathEvent, YogPlayerDeathFn, YogPlayerFn,
YogPlayerMoveEvent, YogPlayerMoveFn, YogPlayerRespawnEvent, YogPlayerRespawnFn,
YogProjectileHitEvent, YogProjectileHitFn, YogScheduledFn, YogScreenFn, YogServer, YogServerFn,
YogStartupGrantDef, YogStr, YogUseBlockFn, YogUseItemFn, YogVec3, YogWorldRenderFn,
};
use yog_registry::{BlockDef, FoodDef, ItemDef};
static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();
static HOT_RELOADER: OnceLock<HotReloader<RuntimeModuleRegistry>> = OnceLock::new();
static CURRENT_REGISTRATION: Mutex<Option<LoadedModule>> = Mutex::new(None);
static MOD_INFOS: Mutex<Vec<[String; 7]>> = Mutex::new(Vec::new());
static INTEROP_SYMBOLS: OnceLock<Mutex<HashMap<String, HashMap<String, usize>>>> = OnceLock::new();
static SERVER: OnceLock<YogServer> = OnceLock::new();
static API_TABLE: OnceLock<YogApi> = OnceLock::new();
static HANDLERS: OnceLock<RwLock<RuntimeHandlers>> = OnceLock::new();
struct GlCtx(glow::Context);
unsafe impl Send for GlCtx {}
unsafe impl Sync for GlCtx {}
static GL: OnceLock<GlCtx> = OnceLock::new();
static GL_GET_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
static GL_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
static GL_GET_PROGRAM_IV: OnceLock<Option<usize>> = OnceLock::new();
#[derive(Debug, Clone)]
pub struct UiLayer {
pub id: String,
pub parent: Option<String>,
pub modal: bool,
pub pause_game: bool,
pub visible: bool,
pub enabled: bool,
pub z_index: i32,
}
type CommandEntry = (LoadedModule, Option<String>, *mut c_void, YogCommandFn);
#[derive(Default)]
struct RuntimeHandlers {
block_break: Vec<(LoadedModule, *mut c_void, YogBlockBreakFn)>,
chat: Vec<(LoadedModule, *mut c_void, YogChatFn)>,
player_join: Vec<(LoadedModule, *mut c_void, YogPlayerFn)>,
player_leave: Vec<(LoadedModule, *mut c_void, YogPlayerFn)>,
use_item: Vec<(LoadedModule, *mut c_void, YogUseItemFn)>,
use_block: Vec<(LoadedModule, *mut c_void, YogUseBlockFn)>,
attack_entity: Vec<(LoadedModule, *mut c_void, YogAttackEntityFn)>,
entity_damage: Vec<(LoadedModule, *mut c_void, YogEntityDamageFn)>,
entity_death: Vec<(LoadedModule, *mut c_void, YogEntityDeathFn)>,
entity_spawn: Vec<(LoadedModule, *mut c_void, YogEntitySpawnFn)>,
player_place_block: Vec<(LoadedModule, *mut c_void, YogPlaceBlockFn)>,
player_death: Vec<(LoadedModule, *mut c_void, YogPlayerDeathFn)>,
player_respawn: Vec<(LoadedModule, *mut c_void, YogPlayerRespawnFn)>,
advancement: Vec<(LoadedModule, *mut c_void, YogAdvancementFn)>,
entity_interact: Vec<(LoadedModule, *mut c_void, YogEntityInteractFn)>,
item_craft: Vec<(LoadedModule, *mut c_void, YogCraftFn)>,
explosion: Vec<(LoadedModule, *mut c_void, YogExplosionFn)>,
item_pickup: Vec<(LoadedModule, *mut c_void, YogItemPickupFn)>,
player_move: Vec<(LoadedModule, *mut c_void, YogPlayerMoveFn)>,
container_open: Vec<(LoadedModule, *mut c_void, YogContainerOpenFn)>,
container_close: Vec<(LoadedModule, *mut c_void, YogContainerCloseFn)>,
projectile_hit: Vec<(LoadedModule, *mut c_void, YogProjectileHitFn)>,
client_tick: Vec<(LoadedModule, *mut c_void, YogClientFn)>,
hud_render: Vec<(LoadedModule, *mut c_void, YogHudRenderFn)>,
world_render: Vec<(LoadedModule, *mut c_void, YogWorldRenderFn)>,
key_press: Vec<(LoadedModule, *mut c_void, YogKeyPressFn)>,
screen_open: Vec<(LoadedModule, *mut c_void, YogScreenFn)>,
screen_close: Vec<(LoadedModule, *mut c_void, YogScreenFn)>,
server_tick: Vec<(LoadedModule, *mut c_void, YogServerFn)>,
server_started: Vec<(LoadedModule, *mut c_void, YogServerFn)>,
server_stopping: Vec<(LoadedModule, *mut c_void, YogServerFn)>,
commands: HashMap<String, Vec<CommandEntry>>,
recipes: Vec<(String, String, String)>,
packets: HashMap<String, (LoadedModule, *mut c_void, YogPacketFn)>,
client_packets: HashMap<String, (LoadedModule, *mut c_void, YogPacketFn)>,
items: Vec<ItemDef>,
blocks: Vec<BlockDef>,
inventories: Vec<yog_inventory::InventoryDef>,
books: HashMap<String, String>,
book_renderers: Mutex<HashMap<String, yog_book::BookRenderer>>,
dimensions: HashMap<String, String>,
chunk_generators: HashMap<String, (LoadedModule, *mut c_void, yog_abi::YogChunkGeneratorFn)>,
ui_handlers: HashMap<String, (LoadedModule, *mut c_void, yog_abi::YogUIEventFn)>,
ui_render_handlers: HashMap<String, Vec<(LoadedModule, *mut c_void, YogHudRenderFn)>>,
menu_entries: Vec<(String, String)>, pub active_uis: Mutex<Vec<UiLayer>>,
startup_grants: Vec<yog_registry::StartupGrant>,
startup_granted: Mutex<HashMap<String, bool>>,
pending_grants: Mutex<Vec<(String, String, u32)>>,
scheduler: Mutex<SchedulerState>,
}
unsafe impl Send for RuntimeHandlers {}
unsafe impl Sync for RuntimeHandlers {}
#[derive(Default)]
struct SchedulerState {
once_tasks: Vec<OnceTask>,
repeating_tasks: Vec<RepeatingTask>,
}
struct OnceTask {
module: LoadedModule,
delay_remaining: u64,
ud: *mut c_void,
f: YogScheduledFn,
}
struct RepeatingTask {
module: LoadedModule,
period: u64,
ticks_left: u64,
ud: *mut c_void,
f: YogScheduledFn,
}
unsafe impl Send for SchedulerState {}
unsafe impl Sync for SchedulerState {}
unsafe impl Send for OnceTask {}
unsafe impl Send for RepeatingTask {}
type Entry<F> = (LoadedModule, *mut c_void, F);
fn each_while<F: Copy>(entries: &[Entry<F>], mut call: impl FnMut(*mut c_void, F) -> bool) -> bool {
for (module, ud, f) in entries {
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
if !call(*ud, *f) {
return false;
}
}
true
}
fn each<F: Copy, R>(entries: &[Entry<F>], mut call: impl FnMut(*mut c_void, F) -> R) {
each_while(entries, |ud, f| {
call(ud, f);
true
});
}
fn allowed<F: Copy>(entries: &[Entry<F>], call: impl FnMut(*mut c_void, F) -> bool) -> bool {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| each_while(entries, call)))
.unwrap_or(true)
}
fn handlers() -> std::sync::RwLockReadGuard<'static, RuntimeHandlers> {
HANDLERS
.get()
.expect("yog: nativeInit not called yet")
.read()
.expect("handlers lock poisoned")
}
fn current_registration() -> LoadedModule {
CURRENT_REGISTRATION
.lock()
.unwrap()
.clone()
.expect("a yog-runtime api_* function was called outside of yog_mod_register")
}
fn hot_reloader() -> &'static HotReloader<RuntimeModuleRegistry> {
HOT_RELOADER.get().expect("yog: nativeInit not called yet")
}
macro_rules! for_each_handler_list {
($h:expr_2021, |$list:ident| $body:expr_2021) => {{
{
let $list = &mut $h.block_break;
$body;
}
{
let $list = &mut $h.chat;
$body;
}
{
let $list = &mut $h.player_join;
$body;
}
{
let $list = &mut $h.player_leave;
$body;
}
{
let $list = &mut $h.use_item;
$body;
}
{
let $list = &mut $h.use_block;
$body;
}
{
let $list = &mut $h.attack_entity;
$body;
}
{
let $list = &mut $h.entity_damage;
$body;
}
{
let $list = &mut $h.entity_death;
$body;
}
{
let $list = &mut $h.entity_spawn;
$body;
}
{
let $list = &mut $h.player_place_block;
$body;
}
{
let $list = &mut $h.player_death;
$body;
}
{
let $list = &mut $h.player_respawn;
$body;
}
{
let $list = &mut $h.advancement;
$body;
}
{
let $list = &mut $h.entity_interact;
$body;
}
{
let $list = &mut $h.item_craft;
$body;
}
{
let $list = &mut $h.explosion;
$body;
}
{
let $list = &mut $h.item_pickup;
$body;
}
{
let $list = &mut $h.player_move;
$body;
}
{
let $list = &mut $h.container_open;
$body;
}
{
let $list = &mut $h.container_close;
$body;
}
{
let $list = &mut $h.projectile_hit;
$body;
}
{
let $list = &mut $h.client_tick;
$body;
}
{
let $list = &mut $h.hud_render;
$body;
}
{
let $list = &mut $h.world_render;
$body;
}
{
let $list = &mut $h.key_press;
$body;
}
{
let $list = &mut $h.screen_open;
$body;
}
{
let $list = &mut $h.screen_close;
$body;
}
{
let $list = &mut $h.server_tick;
$body;
}
{
let $list = &mut $h.server_started;
$body;
}
{
let $list = &mut $h.server_stopping;
$body;
}
}};
}
struct RuntimeModuleRegistry;
impl ModuleRegistry for RuntimeModuleRegistry {
fn retarget(&self, _mod_id: &str, _from: ModuleGeneration, _to: ModuleGeneration) {}
fn purge(&self, generation: ModuleGeneration) {
let Some(lock) = HANDLERS.get() else { return };
let mut h = lock.write().expect("handlers lock poisoned");
let h = &mut *h;
for_each_handler_list!(h, |list| list
.retain(|(m, ..)| m.generation() != generation));
for entries in h.commands.values_mut() {
entries.retain(|(m, ..)| m.generation() != generation);
}
h.packets.retain(|_, (m, ..)| m.generation() != generation);
h.client_packets
.retain(|_, (m, ..)| m.generation() != generation);
h.chunk_generators
.retain(|_, (m, ..)| m.generation() != generation);
h.ui_handlers
.retain(|_, (m, ..)| m.generation() != generation);
for entries in h.ui_render_handlers.values_mut() {
entries.retain(|(m, ..)| m.generation() != generation);
}
h.scheduler
.lock()
.expect("scheduler poisoned")
.once_tasks
.retain(|t| t.module.generation() != generation);
h.scheduler
.lock()
.expect("scheduler poisoned")
.repeating_tasks
.retain(|t| t.module.generation() != generation);
}
}
fn guard(label: &str, f: impl FnOnce()) {
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).is_err() {
yog_logging::error!("a mod panicked handling `{}` (ignored)", label);
}
}
macro_rules! jstr {
($env:expr_2021, $s:expr_2021) => {
jstr!($env, $s, Default::default())
};
($env:expr_2021, $s:expr_2021, $or:expr_2021) => {
match $env.get_string(&$s) {
Ok(s) => String::from(s),
Err(_) => return $or,
}
};
}
fn get_env() -> Option<jni::AttachGuard<'static>> {
JAVA_VM.get()?.attach_current_thread().ok()
}
macro_rules! host {
($class:literal, $name:literal, [$($arg:expr_2021),* $(,)?]) => {
match $crate::get_env() {
Some(mut env) => $crate::jcall::jcall!(&mut env, $class, $name, [$($arg),*]),
None => None,
}
};
}
macro_rules! bridge {
($name:literal, [$($arg:expr_2021),* $(,)?]) => {
host!("dev/yog/NativeBridge", $name, [$($arg),*])
};
}
fn owned(s: Option<String>) -> YogOwnedStr {
s.map_or(YogOwnedStr::NONE, YogOwnedStr::from_string)
}
fn parse_tsv<T: std::str::FromStr, const N: usize>(s: &str) -> Option<[T; N]> {
let values: Vec<T> = s
.split('\t')
.take(N)
.map(|v| v.parse().ok())
.collect::<Option<_>>()?;
values.try_into().ok()
}
unsafe extern "C" fn yog_free_str(ptr: *mut u8, len: u32) {
unsafe {
if !ptr.is_null() {
drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
ptr,
len as usize,
)));
}
}
}
unsafe extern "C" fn srv_broadcast(_ctx: *mut c_void, msg: YogStr) {
unsafe {
let _: Option<()> = bridge!("broadcast", [S(msg)]);
}
}
unsafe extern "C" fn srv_get_block(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
) -> YogOwnedStr {
unsafe { owned(bridge!("getBlock", [S(dim), I(pos.x), I(pos.y), I(pos.z)])) }
}
unsafe extern "C" fn srv_set_block(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
block: YogStr,
) -> bool {
unsafe {
bridge!("setBlock", [S(dim), I(pos.x), I(pos.y), I(pos.z), S(block)]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_world_time(_ctx: *mut c_void, dim: YogStr, out: *mut i64) -> bool {
unsafe {
match bridge!("worldTime", [S(dim)]) {
Some(v) if v != i64::MIN => {
*out = v;
true
}
_ => false,
}
}
}
unsafe extern "C" fn srv_set_time(_ctx: *mut c_void, dim: YogStr, time: i64) -> bool {
unsafe { bridge!("worldSetTime", [S(dim), J(time)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_is_raining(_ctx: *mut c_void, dim: YogStr) -> bool {
unsafe { bridge!("worldIsRaining", [S(dim)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_set_weather(
_ctx: *mut c_void,
dim: YogStr,
raining: bool,
dur: i32,
) -> bool {
unsafe { bridge!("worldSetWeather", [S(dim), Z(raining), I(dur)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_give_item(
_ctx: *mut c_void,
player: YogStr,
item: YogStr,
count: u32,
) -> bool {
unsafe { bridge!("giveItem", [S(player), S(item), I(count as i32)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_player_teleport(_ctx: *mut c_void, player: YogStr, pos: YogVec3) -> bool {
unsafe { bridge!("teleport", [S(player), D(pos.x), D(pos.y), D(pos.z)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_send_to_player(
_ctx: *mut c_void,
player: YogStr,
channel: YogStr,
data: *const u8,
len: u32,
) -> bool {
unsafe {
bridge!(
"sendToPlayer",
[
S(player),
S(channel),
Bytes(std::slice::from_raw_parts(data, len as usize))
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_send_to_server(
_ctx: *mut c_void,
channel: YogStr,
data: *const u8,
len: u32,
) -> bool {
unsafe {
host!(
"dev/yog/YogClient",
"sendToServer",
[
S(channel),
Bytes(std::slice::from_raw_parts(data, len as usize))
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_kick_player(_ctx: *mut c_void, player: YogStr, reason: YogStr) -> bool {
unsafe { bridge!("kickPlayer", [S(player), S(reason)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_set_gamemode(_ctx: *mut c_void, player: YogStr, mode: YogStr) -> bool {
unsafe { bridge!("setGamemode", [S(player), S(mode)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_send_title(
_ctx: *mut c_void,
player: YogStr,
title: YogStr,
sub: YogStr,
fi: i32,
stay: i32,
fo: i32,
) -> bool {
unsafe {
bridge!(
"sendTitle",
[S(player), S(title), S(sub), I(fi), I(stay), I(fo)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_send_actionbar(_ctx: *mut c_void, player: YogStr, msg: YogStr) -> bool {
unsafe { bridge!("sendActionbar", [S(player), S(msg)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_play_sound(
_ctx: *mut c_void,
dim: YogStr,
pos: YogVec3,
sound: YogStr,
vol: f32,
pitch: f32,
) -> bool {
unsafe {
bridge!(
"playSound",
[
S(dim),
D(pos.x),
D(pos.y),
D(pos.z),
S(sound),
F(vol),
F(pitch)
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_play_sound_player(
_ctx: *mut c_void,
player: YogStr,
sound: YogStr,
vol: f32,
pitch: f32,
) -> bool {
unsafe {
bridge!("playSoundToPlayer", [S(player), S(sound), F(vol), F(pitch)]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_entity_teleport(_ctx: *mut c_void, uuid: YogStr, pos: YogVec3) -> bool {
unsafe { bridge!("entityTeleport", [S(uuid), D(pos.x), D(pos.y), D(pos.z)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_entity_rotation(
_ctx: *mut c_void,
uuid: YogStr,
out: *mut YogVec3,
) -> bool {
unsafe {
let Some(s): Option<String> = bridge!("entityRotation", [S(uuid)]) else {
return false;
};
match parse_tsv::<f64, 2>(&s) {
Some([yaw, pitch]) => {
*out = YogVec3 {
x: yaw,
y: pitch,
z: 0.0,
};
true
}
None => false,
}
}
}
unsafe extern "C" fn srv_entity_position(
_ctx: *mut c_void,
uuid: YogStr,
out: *mut YogVec3,
) -> bool {
unsafe {
let Some(s): Option<String> = bridge!("entityPosition", [S(uuid)]) else {
return false;
};
match parse_tsv::<f64, 3>(&s) {
Some([x, y, z]) => {
*out = YogVec3 { x, y, z };
true
}
None => false,
}
}
}
unsafe extern "C" fn srv_entity_health(_ctx: *mut c_void, uuid: YogStr, out: *mut f32) -> bool {
unsafe {
match bridge!("entityHealth", [S(uuid)]) {
Some(v) if !f64::is_nan(v) => {
*out = v as f32;
true
}
_ => false,
}
}
}
unsafe extern "C" fn srv_entity_set_health(_ctx: *mut c_void, uuid: YogStr, hp: f32) -> bool {
unsafe { bridge!("entitySetHealth", [S(uuid), D(hp as f64)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_entity_kill(_ctx: *mut c_void, uuid: YogStr) -> bool {
unsafe { bridge!("entityKill", [S(uuid)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_spawn_entity(
_ctx: *mut c_void,
type_id: YogStr,
dim: YogStr,
pos: YogVec3,
) -> YogOwnedStr {
unsafe {
owned(bridge!(
"spawnEntity",
[S(type_id), S(dim), D(pos.x), D(pos.y), D(pos.z)]
))
}
}
unsafe extern "C" fn srv_entity_add_effect(
_ctx: *mut c_void,
uuid: YogStr,
fx: YogStr,
dur: i32,
amp: u8,
particles: bool,
) -> bool {
unsafe {
bridge!(
"entityAddEffect",
[S(uuid), S(fx), I(dur), I(amp as i32), Z(particles)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_entity_remove_effect(_ctx: *mut c_void, uuid: YogStr, fx: YogStr) -> bool {
unsafe { bridge!("entityRemoveEffect", [S(uuid), S(fx)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_entity_clear_effects(_ctx: *mut c_void, uuid: YogStr) -> bool {
unsafe { bridge!("entityClearEffects", [S(uuid)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_entity_velocity(
_ctx: *mut c_void,
uuid: YogStr,
out: *mut YogVec3,
) -> bool {
unsafe {
let Some(s): Option<String> = bridge!("entityVelocity", [S(uuid)]) else {
return false;
};
match parse_tsv::<f64, 3>(&s) {
Some([x, y, z]) => {
*out = YogVec3 { x, y, z };
true
}
None => false,
}
}
}
unsafe extern "C" fn srv_entity_set_velocity(
_ctx: *mut c_void,
uuid: YogStr,
vel: YogVec3,
) -> bool {
unsafe {
bridge!("entitySetVelocity", [S(uuid), D(vel.x), D(vel.y), D(vel.z)]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_entity_add_velocity(
_ctx: *mut c_void,
uuid: YogStr,
vel: YogVec3,
) -> bool {
unsafe {
bridge!("entityAddVelocity", [S(uuid), D(vel.x), D(vel.y), D(vel.z)]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_has_item_tag(_ctx: *mut c_void, item: YogStr, tag: YogStr) -> bool {
unsafe { bridge!("hasItemTag", [S(item), S(tag)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_has_block_tag(_ctx: *mut c_void, block: YogStr, tag: YogStr) -> bool {
unsafe { bridge!("hasBlockTag", [S(block), S(tag)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_drop_loot(
_ctx: *mut c_void,
table: YogStr,
dim: YogStr,
pos: YogVec3,
) -> bool {
unsafe {
bridge!("dropLoot", [S(table), S(dim), D(pos.x), D(pos.y), D(pos.z)]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_scoreboard_get(
_ctx: *mut c_void,
obj: YogStr,
player: YogStr,
out: *mut i32,
) -> bool {
unsafe {
match bridge!("scoreboardGet", [S(obj), S(player)]) {
Some(v) if v != i32::MIN => {
*out = v;
true
}
_ => false,
}
}
}
unsafe extern "C" fn srv_scoreboard_set(
_ctx: *mut c_void,
obj: YogStr,
player: YogStr,
score: i32,
) -> bool {
unsafe { bridge!("scoreboardSet", [S(obj), S(player), I(score)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_scoreboard_add(
_ctx: *mut c_void,
obj: YogStr,
player: YogStr,
delta: i32,
out: *mut i32,
) -> bool {
unsafe {
match bridge!("scoreboardAdd", [S(obj), S(player), I(delta)]) {
Some(v) if v != i32::MIN => {
*out = v;
true
}
_ => false,
}
}
}
unsafe extern "C" fn srv_bossbar_create(
_ctx: *mut c_void,
id: YogStr,
title: YogStr,
color: YogStr,
style: YogStr,
) -> bool {
unsafe { bridge!("bossbarCreate", [S(id), S(title), S(color), S(style)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_remove(_ctx: *mut c_void, id: YogStr) -> bool {
unsafe { bridge!("bossbarRemove", [S(id)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_set_title(_ctx: *mut c_void, id: YogStr, title: YogStr) -> bool {
unsafe { bridge!("bossbarSetTitle", [S(id), S(title)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_set_progress(
_ctx: *mut c_void,
id: YogStr,
progress: f32,
) -> bool {
unsafe { bridge!("bossbarSetProgress", [S(id), F(progress)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_set_color(_ctx: *mut c_void, id: YogStr, color: YogStr) -> bool {
unsafe { bridge!("bossbarSetColor", [S(id), S(color)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_add_player(_ctx: *mut c_void, id: YogStr, player: YogStr) -> bool {
unsafe { bridge!("bossbarAddPlayer", [S(id), S(player)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_remove_player(
_ctx: *mut c_void,
id: YogStr,
player: YogStr,
) -> bool {
unsafe { bridge!("bossbarRemovePlayer", [S(id), S(player)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_bossbar_set_visible(_ctx: *mut c_void, id: YogStr, visible: bool) -> bool {
unsafe { bridge!("bossbarSetVisible", [S(id), Z(visible)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_get_block_nbt(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
) -> YogOwnedStr {
unsafe {
owned(bridge!(
"getBlockNbt",
[S(dim), I(pos.x), I(pos.y), I(pos.z)]
))
}
}
unsafe extern "C" fn srv_set_block_nbt(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
snbt: YogStr,
) -> bool {
unsafe {
bridge!(
"setBlockNbt",
[S(dim), I(pos.x), I(pos.y), I(pos.z), S(snbt)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_get_inventory_slot(
_ctx: *mut c_void,
dim: YogStr,
pos: yog_abi::YogBlockPos,
slot: u32,
) -> YogOwnedStr {
unsafe {
owned(bridge!(
"getInventorySlot",
[S(dim), I(pos.x), I(pos.y), I(pos.z), I(slot as i32)]
))
}
}
unsafe extern "C" fn srv_set_inventory_slot(
_ctx: *mut c_void,
dim: YogStr,
pos: yog_abi::YogBlockPos,
slot: u32,
item_id: YogStr,
count: u32,
) -> bool {
unsafe {
bridge!(
"setInventorySlot",
[
S(dim),
I(pos.x),
I(pos.y),
I(pos.z),
I(slot as i32),
S(item_id),
I(count as i32)
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_player_inventory(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("playerInventory", [S(player)])) }
}
unsafe extern "C" fn srv_player_set_slot(
_ctx: *mut c_void,
player: YogStr,
slot: u32,
item_id: YogStr,
count: u32,
) -> bool {
unsafe {
bridge!(
"playerSetSlot",
[S(player), I(slot as i32), S(item_id), I(count as i32)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_player_teleport_dim(
_ctx: *mut c_void,
player: YogStr,
dim: YogStr,
pos: YogVec3,
) -> bool {
unsafe {
bridge!(
"teleportToDim",
[S(player), S(dim), D(pos.x), D(pos.y), D(pos.z)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_entity_teleport_dim(
_ctx: *mut c_void,
uuid: YogStr,
dim: YogStr,
pos: YogVec3,
) -> bool {
unsafe {
bridge!(
"entityTeleportToDim",
[S(uuid), S(dim), D(pos.x), D(pos.y), D(pos.z)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_online_players(_ctx: *mut c_void) -> YogOwnedStr {
unsafe { owned(bridge!("onlinePlayers", [])) }
}
unsafe extern "C" fn srv_world_entity_count(
_ctx: *mut c_void,
dim: YogStr,
entity_type: YogStr,
) -> i32 {
unsafe { bridge!("worldEntityCount", [S(dim), S(entity_type)]).unwrap_or(-1) }
}
unsafe extern "C" fn srv_game_dir(_ctx: *mut c_void) -> YogOwnedStr {
unsafe { owned(bridge!("gameDir", [])) }
}
unsafe extern "C" fn srv_entity_get_nbt(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("entityGetNbt", [S(uuid)])) }
}
unsafe extern "C" fn srv_entity_set_nbt(_ctx: *mut c_void, uuid: YogStr, snbt: YogStr) -> bool {
unsafe { bridge!("entitySetNbt", [S(uuid), S(snbt)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_spawn_particles(
_ctx: *mut c_void,
dim: YogStr,
pos: YogVec3,
particle_type: YogStr,
count: i32,
dx: f64,
dy: f64,
dz: f64,
speed: f64,
) -> bool {
unsafe {
bridge!(
"spawnParticles",
[
S(dim),
D(pos.x),
D(pos.y),
D(pos.z),
S(particle_type),
I(count),
D(dx),
D(dy),
D(dz),
D(speed)
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_entity_attribute_get(
_ctx: *mut c_void,
uuid: YogStr,
attr: YogStr,
) -> f64 {
unsafe { bridge!("entityAttributeGet", [S(uuid), S(attr)]).unwrap_or(f64::NAN) }
}
unsafe extern "C" fn srv_entity_attribute_set(
_ctx: *mut c_void,
uuid: YogStr,
attr: YogStr,
value: f64,
) -> bool {
unsafe { bridge!("entityAttributeSet", [S(uuid), S(attr), D(value)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_get_held_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("getHeldItemNbt", [S(player)])) }
}
unsafe extern "C" fn srv_set_held_item_nbt(
_ctx: *mut c_void,
player: YogStr,
snbt: YogStr,
) -> bool {
unsafe { bridge!("setHeldItemNbt", [S(player), S(snbt)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_get_offhand_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("getOffhandItemNbt", [S(player)])) }
}
unsafe extern "C" fn srv_set_offhand_item_nbt(
_ctx: *mut c_void,
player: YogStr,
snbt: YogStr,
) -> bool {
unsafe { bridge!("setOffhandItemNbt", [S(player), S(snbt)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_get_slot_item(
_ctx: *mut c_void,
player: YogStr,
slot: u32,
) -> YogOwnedStr {
unsafe { owned(bridge!("getSlotItem", [S(player), I(slot as i32)])) }
}
unsafe extern "C" fn srv_set_slot_item(
_ctx: *mut c_void,
player: YogStr,
slot: u32,
item_id: YogStr,
count: u32,
snbt: YogStr,
) -> bool {
unsafe {
bridge!(
"setSlotItem",
[
S(player),
I(slot as i32),
S(item_id),
I(count as i32),
S(snbt)
]
)
.unwrap_or(false)
}
}
fn gl_draw_mode(mode: u8) -> u32 {
match mode {
1 => glow::LINES,
2 => glow::LINE_STRIP,
3 => glow::TRIANGLE_STRIP,
4 => glow::TRIANGLE_FAN,
_ => glow::TRIANGLES,
}
}
fn gl_attr_type(dtype: u8) -> u32 {
match dtype {
1 => glow::UNSIGNED_BYTE,
2 => glow::INT,
3 => glow::UNSIGNED_INT,
_ => glow::FLOAT,
}
}
unsafe fn compile_shader(gl: &glow::Context, stage: u32, src: &str) -> Option<glow::NativeShader> {
unsafe {
let sh = gl.create_shader(stage).ok()?;
gl.shader_source(sh, src);
gl.compile_shader(sh);
if !gl.get_shader_compile_status(sh) {
yog_logging::error!("yog-gfx shader compile: {}", gl.get_shader_info_log(sh));
gl.delete_shader(sh);
return None;
}
Some(sh)
}
}
unsafe extern "C" fn gfx_buf_create() -> u32 {
unsafe {
let Some(g) = GL.get() else { return 0 };
g.0.create_buffer().map(|b| b.0.get()).unwrap_or(0)
}
}
unsafe extern "C" fn gfx_buf_delete(handle: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new(handle) else {
return;
};
g.0.delete_buffer(glow::NativeBuffer(n));
}
}
unsafe extern "C" fn gfx_buf_data(handle: u32, bytes: *const u8, len: u32, dynamic: bool) {
unsafe {
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new(handle) else {
return;
};
let gl = &g.0;
let data = std::slice::from_raw_parts(bytes, len as usize);
let usage = if dynamic {
glow::DYNAMIC_DRAW
} else {
glow::STATIC_DRAW
};
gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, data, usage);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
}
}
unsafe extern "C" fn gfx_buf_subdata(handle: u32, offset: u32, bytes: *const u8, len: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new(handle) else {
return;
};
let gl = &g.0;
let data = std::slice::from_raw_parts(bytes, len as usize);
gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, offset as i32, data);
gl.bind_buffer(glow::ARRAY_BUFFER, None);
}
}
unsafe extern "C" fn gfx_vao_create() -> u32 {
unsafe {
let Some(g) = GL.get() else { return 0 };
g.0.create_vertex_array().map(|v| v.0.get()).unwrap_or(0)
}
}
unsafe extern "C" fn gfx_vao_delete(handle: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new(handle) else {
return;
};
g.0.delete_vertex_array(glow::NativeVertexArray(n));
}
}
unsafe extern "C" fn gfx_vao_attrib(
vao: u32,
vbo: u32,
index: u32,
components: u8,
dtype: u8,
normalized: bool,
stride: u32,
offset: u32,
) {
unsafe {
let Some(g) = GL.get() else { return };
let (Some(vn), Some(bn)) = (NonZeroU32::new(vao), NonZeroU32::new(vbo)) else {
return;
};
let gl = &g.0;
gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(bn)));
let gl_type = gl_attr_type(dtype);
if dtype == 2 || dtype == 3 {
gl.vertex_attrib_pointer_i32(
index,
components as i32,
gl_type,
stride as i32,
offset as i32,
);
} else {
gl.vertex_attrib_pointer_f32(
index,
components as i32,
gl_type,
normalized,
stride as i32,
offset as i32,
);
}
gl.enable_vertex_attrib_array(index);
gl.bind_vertex_array(None);
}
}
unsafe extern "C" fn gfx_vao_set_ebo(vao: u32, ebo: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let (Some(vn), Some(en)) = (NonZeroU32::new(vao), NonZeroU32::new(ebo)) else {
return;
};
let gl = &g.0;
gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(glow::NativeBuffer(en)));
gl.bind_vertex_array(None);
}
}
fn shader_cache_path(vert: &str, frag: &str) -> Option<PathBuf> {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
vert.hash(&mut h);
frag.hash(&mut h);
let hash = h.finish();
let dir = std::env::var("HOME")
.map(|home| {
PathBuf::from(home)
.join(".cache")
.join("yog")
.join("shaders")
})
.ok()?;
std::fs::create_dir_all(&dir).ok()?;
Some(dir.join(format!("{hash:016x}.ysc")))
}
unsafe fn load_shader_binary(gl: &glow::Context, data: &[u8]) -> Option<glow::NativeProgram> {
unsafe {
if data.len() < 4 {
return None;
}
type ProgramBinaryFn = unsafe extern "system" fn(u32, u32, *const c_void, i32);
let fn_ptr = (*GL_PROGRAM_BINARY.get()?)?;
let program_binary: ProgramBinaryFn = std::mem::transmute(fn_ptr);
let fmt = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let prog = gl.create_program().ok()?;
program_binary(
prog.0.get(),
fmt,
data[4..].as_ptr() as *const c_void,
(data.len() - 4) as i32,
);
if gl.get_program_link_status(prog) {
Some(prog)
} else {
gl.delete_program(prog);
None
}
}
}
unsafe fn get_shader_binary(prog: glow::NativeProgram) -> (Vec<u8>, u32) {
unsafe {
type GetProgramivFn = unsafe extern "system" fn(u32, u32, *mut i32);
type GetProgramBinaryFn =
unsafe extern "system" fn(u32, i32, *mut i32, *mut u32, *mut c_void);
let Some(get_iv_raw) = GL_GET_PROGRAM_IV.get().and_then(|v| *v) else {
return (vec![], 0);
};
let Some(get_bin_raw) = GL_GET_PROGRAM_BINARY.get().and_then(|v| *v) else {
return (vec![], 0);
};
let get_program_iv: GetProgramivFn = std::mem::transmute(get_iv_raw);
let get_program_binary: GetProgramBinaryFn = std::mem::transmute(get_bin_raw);
const PROGRAM_BINARY_LENGTH: u32 = 0x8741;
let mut size: i32 = 0;
get_program_iv(prog.0.get(), PROGRAM_BINARY_LENGTH, &mut size);
if size <= 0 {
return (vec![], 0);
}
let mut buf = vec![0u8; size as usize];
let mut actual_len: i32 = 0;
let mut fmt: u32 = 0;
get_program_binary(
prog.0.get(),
size,
&mut actual_len,
&mut fmt,
buf.as_mut_ptr() as *mut c_void,
);
buf.truncate(actual_len.max(0) as usize);
(buf, fmt)
}
}
unsafe extern "C" fn gfx_prog_create(vert: YogStr, frag: YogStr, out: *mut u32) -> bool {
unsafe {
let Some(g) = GL.get() else { return false };
let gl = &g.0;
let vert_s = vert.as_str();
let frag_s = frag.as_str();
let cache_path = shader_cache_path(vert_s, frag_s);
if let Some(ref path) = cache_path
&& let Ok(data) = std::fs::read(path)
{
if let Some(prog) = load_shader_binary(gl, &data) {
*out = prog.0.get();
return true;
}
let _ = std::fs::remove_file(path);
}
let vs = match compile_shader(gl, glow::VERTEX_SHADER, vert_s) {
Some(s) => s,
None => return false,
};
let fs = match compile_shader(gl, glow::FRAGMENT_SHADER, frag_s) {
Some(s) => s,
None => {
gl.delete_shader(vs);
return false;
}
};
let prog = match gl.create_program() {
Ok(p) => p,
Err(e) => {
yog_logging::error!("yog-gfx: create_program: {}", e);
gl.delete_shader(vs);
gl.delete_shader(fs);
return false;
}
};
gl.attach_shader(prog, vs);
gl.attach_shader(prog, fs);
gl.link_program(prog);
gl.detach_shader(prog, vs);
gl.detach_shader(prog, fs);
gl.delete_shader(vs);
gl.delete_shader(fs);
if !gl.get_program_link_status(prog) {
yog_logging::error!("yog-gfx: shader link: {}", gl.get_program_info_log(prog));
gl.delete_program(prog);
return false;
}
if let Some(ref path) = cache_path {
let (binary, fmt) = get_shader_binary(prog);
if !binary.is_empty() {
let mut blob = fmt.to_le_bytes().to_vec();
blob.extend_from_slice(&binary);
let _ = std::fs::write(path, &blob);
}
}
*out = prog.0.get();
true
}
}
unsafe extern "C" fn gfx_prog_delete(handle: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new(handle) else {
return;
};
g.0.delete_program(glow::NativeProgram(n));
}
}
macro_rules! with_prog {
($handle:expr_2021, |$gl:ident, $prog:ident, $loc:ident ($name:expr_2021)| $body:expr_2021) => {{
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new($handle) else {
return;
};
let $gl = &g.0;
let $prog = glow::NativeProgram(n);
$gl.use_program(Some($prog));
let $loc = $gl.get_uniform_location($prog, $name.as_str());
$body
}};
}
unsafe extern "C" fn gfx_prog_uniform_1i(prog: u32, name: YogStr, v: i32) {
unsafe {
with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_i32(loc.as_ref(), v));
}
}
unsafe extern "C" fn gfx_prog_uniform_1f(prog: u32, name: YogStr, v: f32) {
unsafe {
with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_f32(loc.as_ref(), v));
}
}
unsafe extern "C" fn gfx_prog_uniform_2f(prog: u32, name: YogStr, x: f32, y: f32) {
unsafe {
with_prog!(prog, |gl, _p, loc(name)| gl.uniform_2_f32(
loc.as_ref(),
x,
y
));
}
}
unsafe extern "C" fn gfx_prog_uniform_3f(prog: u32, name: YogStr, x: f32, y: f32, z: f32) {
unsafe {
with_prog!(prog, |gl, _p, loc(name)| gl.uniform_3_f32(
loc.as_ref(),
x,
y,
z
));
}
}
unsafe extern "C" fn gfx_prog_uniform_4f(prog: u32, name: YogStr, x: f32, y: f32, z: f32, w: f32) {
unsafe {
with_prog!(prog, |gl, _p, loc(name)| gl.uniform_4_f32(
loc.as_ref(),
x,
y,
z,
w
));
}
}
unsafe extern "C" fn gfx_prog_uniform_mat4(prog: u32, name: YogStr, col_major: *const f32) {
unsafe {
with_prog!(prog, |gl, _p, loc(name)| {
let data = std::slice::from_raw_parts(col_major, 16);
gl.uniform_matrix_4_f32_slice(loc.as_ref(), false, data);
});
}
}
unsafe extern "C" fn gfx_tex_create(w: u32, h: u32, rgba: *const u8, linear: bool) -> u32 {
unsafe {
let Some(g) = GL.get() else { return 0 };
let gl = &g.0;
let tex = match gl.create_texture() {
Ok(t) => t,
Err(_) => return 0,
};
gl.bind_texture(glow::TEXTURE_2D, Some(tex));
let filter = if linear { glow::LINEAR } else { glow::NEAREST };
gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, filter as i32);
gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, filter as i32);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_S,
glow::CLAMP_TO_EDGE as i32,
);
gl.tex_parameter_i32(
glow::TEXTURE_2D,
glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as i32,
);
let pixels = std::slice::from_raw_parts(rgba, (w * h * 4) as usize);
gl.tex_image_2d(
glow::TEXTURE_2D,
0,
glow::RGBA8 as i32,
w as i32,
h as i32,
0,
glow::RGBA,
glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice(Some(pixels)),
);
gl.bind_texture(glow::TEXTURE_2D, None);
tex.0.get()
}
}
unsafe extern "C" fn gfx_tex_delete(handle: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let Some(n) = NonZeroU32::new(handle) else {
return;
};
g.0.delete_texture(glow::NativeTexture(n));
}
}
unsafe extern "C" fn gfx_tex_bind(unit: u32, handle: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let gl = &g.0;
gl.active_texture(glow::TEXTURE0 + unit);
match NonZeroU32::new(handle) {
Some(n) => gl.bind_texture(glow::TEXTURE_2D, Some(glow::NativeTexture(n))),
None => gl.bind_texture(glow::TEXTURE_2D, None),
}
}
}
unsafe extern "C" fn gfx_tex_from_mc(id: YogStr) -> u32 {
unsafe { host!("dev/yog/NativeDraw", "getMcTextureId", [S(id)]).map_or(0, |id: i32| id as u32) }
}
unsafe extern "C" fn gfx_draw_arrays(vao: u32, prog: u32, mode: u8, first: u32, count: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else {
return;
};
let gl = &g.0;
gl.use_program(Some(glow::NativeProgram(pn)));
gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
gl.draw_arrays(gl_draw_mode(mode), first as i32, count as i32);
gl.bind_vertex_array(None);
}
}
unsafe extern "C" fn gfx_draw_elements(
vao: u32,
ebo: u32,
prog: u32,
mode: u8,
count: u32,
u32_idx: bool,
) {
unsafe {
let Some(g) = GL.get() else { return };
let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else {
return;
};
let gl = &g.0;
gl.use_program(Some(glow::NativeProgram(pn)));
gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
let _ = ebo;
let idx_type = if u32_idx {
glow::UNSIGNED_INT
} else {
glow::UNSIGNED_SHORT
};
gl.draw_elements(gl_draw_mode(mode), count as i32, idx_type, 0);
gl.bind_vertex_array(None);
}
}
unsafe extern "C" fn gfx_set_blend(enabled: bool, src: u32, dst: u32) {
unsafe {
let Some(g) = GL.get() else { return };
let gl = &g.0;
if enabled {
gl.enable(glow::BLEND);
gl.blend_func(src, dst);
} else {
gl.disable(glow::BLEND);
}
}
}
unsafe extern "C" fn gfx_set_depth(test: bool, write: bool) {
unsafe {
let Some(g) = GL.get() else { return };
let gl = &g.0;
if test {
gl.enable(glow::DEPTH_TEST);
} else {
gl.disable(glow::DEPTH_TEST);
}
gl.depth_mask(write);
if !test {
gl.disable(glow::CULL_FACE);
}
}
}
unsafe extern "C" fn gfx_set_scissor(x: i32, y: i32, w: i32, h: i32) {
unsafe {
let Some(g) = GL.get() else { return };
let gl = &g.0;
gl.enable(glow::SCISSOR_TEST);
gl.scissor(x, y, w, h);
}
}
unsafe extern "C" fn gfx_clear_scissor() {
unsafe {
let Some(g) = GL.get() else { return };
g.0.disable(glow::SCISSOR_TEST);
}
}
unsafe extern "C" fn gfx_set_viewport(x: i32, y: i32, w: i32, h: i32) {
unsafe {
let Some(g) = GL.get() else { return };
g.0.viewport(x, y, w, h);
}
}
unsafe extern "C" fn gfx_draw2d_rect(x1: f32, y1: f32, x2: f32, y2: f32, color: u32) {
unsafe {
let _: Option<()> = host!(
"dev/yog/NativeDraw",
"drawRect",
[F(x1), F(y1), F(x2), F(y2), I(color as i32)]
);
}
}
unsafe extern "C" fn gfx_draw2d_gradient(
x1: f32,
y1: f32,
x2: f32,
y2: f32,
top: u32,
bottom: u32,
) {
unsafe {
let _: Option<()> = host!(
"dev/yog/NativeDraw",
"drawGradientRect",
[F(x1), F(y1), F(x2), F(y2), I(top as i32), I(bottom as i32)]
);
}
}
unsafe extern "C" fn gfx_draw2d_text(text: YogStr, x: f32, y: f32, color: u32, shadow: bool) {
unsafe {
let _: Option<()> = host!(
"dev/yog/NativeDraw",
"drawText",
[S(text), F(x), F(y), I(color as i32), Z(shadow)]
);
}
}
unsafe extern "C" fn gfx_draw2d_text_font(
text: YogStr,
font: YogStr,
x: f32,
y: f32,
color: u32,
shadow: bool,
) {
unsafe {
let _: Option<()> = host!(
"dev/yog/NativeDraw",
"drawTextFont",
[S(text), S(font), F(x), F(y), I(color as i32), Z(shadow)]
);
}
}
unsafe extern "C" fn gfx_text_width(text: YogStr, font: YogStr) -> f32 {
unsafe { host!("dev/yog/NativeDraw", "textWidth", [S(text), S(font)]).unwrap_or(0.0) }
}
unsafe extern "C" fn gfx_draw2d_item(id: YogStr, x: f32, y: f32, size: f32) {
unsafe {
let _: Option<()> = host!(
"dev/yog/NativeDraw",
"drawItem",
[S(id), F(x), F(y), F(size)]
);
}
}
static GFX_FN_TABLE: YogGfxApi = YogGfxApi {
screen_w: 0,
screen_h: 0,
delta_tick: 0.0,
scale_factor: 1.0,
view_proj: [0.0; 16],
camera_pos: [0.0; 3],
player_pos: [0.0; 3],
_pad1: 0.0,
buf_create: gfx_buf_create,
buf_delete: gfx_buf_delete,
buf_data: gfx_buf_data,
buf_subdata: gfx_buf_subdata,
vao_create: gfx_vao_create,
vao_delete: gfx_vao_delete,
vao_attrib: gfx_vao_attrib,
vao_set_ebo: gfx_vao_set_ebo,
prog_create: gfx_prog_create,
prog_delete: gfx_prog_delete,
prog_uniform_1i: gfx_prog_uniform_1i,
prog_uniform_1f: gfx_prog_uniform_1f,
prog_uniform_2f: gfx_prog_uniform_2f,
prog_uniform_3f: gfx_prog_uniform_3f,
prog_uniform_4f: gfx_prog_uniform_4f,
prog_uniform_mat4: gfx_prog_uniform_mat4,
tex_create: gfx_tex_create,
tex_delete: gfx_tex_delete,
tex_bind: gfx_tex_bind,
tex_from_mc: gfx_tex_from_mc,
draw_arrays: gfx_draw_arrays,
draw_elements: gfx_draw_elements,
set_blend: gfx_set_blend,
set_depth: gfx_set_depth,
set_scissor: gfx_set_scissor,
clear_scissor: gfx_clear_scissor,
set_viewport: gfx_set_viewport,
draw2d_rect: gfx_draw2d_rect,
draw2d_gradient: gfx_draw2d_gradient,
draw2d_text: gfx_draw2d_text,
draw2d_item: gfx_draw2d_item,
draw2d_text_font: gfx_draw2d_text_font,
text_width: gfx_text_width,
draw2d_image: images::gfx_draw2d_image,
image_size: images::gfx_image_size,
};
macro_rules! api_event {
($name:ident, $field:ident, $fn_ty:ty) => {
unsafe extern "C" fn $name(ctx: *mut c_void, ud: *mut c_void, h: $fn_ty) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
handlers.$field.push((current_registration(), ud, h));
}
}
};
}
api_event!(api_on_block_break, block_break, YogBlockBreakFn);
api_event!(api_on_chat, chat, YogChatFn);
api_event!(api_on_player_join, player_join, YogPlayerFn);
api_event!(api_on_player_leave, player_leave, YogPlayerFn);
api_event!(api_on_use_item, use_item, YogUseItemFn);
api_event!(api_on_use_block, use_block, YogUseBlockFn);
api_event!(api_on_attack_entity, attack_entity, YogAttackEntityFn);
api_event!(api_on_entity_damage, entity_damage, YogEntityDamageFn);
api_event!(api_on_entity_death, entity_death, YogEntityDeathFn);
api_event!(api_on_entity_spawn, entity_spawn, YogEntitySpawnFn);
api_event!(
api_on_player_place_block,
player_place_block,
YogPlaceBlockFn
);
api_event!(api_on_player_death, player_death, YogPlayerDeathFn);
api_event!(api_on_player_respawn, player_respawn, YogPlayerRespawnFn);
api_event!(api_on_advancement, advancement, YogAdvancementFn);
api_event!(api_on_entity_interact, entity_interact, YogEntityInteractFn);
api_event!(api_on_item_craft, item_craft, YogCraftFn);
api_event!(api_on_explosion, explosion, YogExplosionFn);
api_event!(api_on_item_pickup, item_pickup, YogItemPickupFn);
api_event!(api_on_player_move, player_move, YogPlayerMoveFn);
api_event!(api_on_container_open, container_open, YogContainerOpenFn);
api_event!(api_on_container_close, container_close, YogContainerCloseFn);
api_event!(api_on_projectile_hit, projectile_hit, YogProjectileHitFn);
api_event!(api_on_client_tick, client_tick, YogClientFn);
api_event!(api_on_hud_render, hud_render, YogHudRenderFn);
api_event!(api_on_world_render, world_render, YogWorldRenderFn);
api_event!(api_on_key_press, key_press, YogKeyPressFn);
api_event!(api_on_screen_open, screen_open, YogScreenFn);
api_event!(api_on_screen_close, screen_close, YogScreenFn);
api_event!(api_on_server_tick, server_tick, YogServerFn);
api_event!(api_on_server_started, server_started, YogServerFn);
api_event!(api_on_server_stopping, server_stopping, YogServerFn);
unsafe extern "C" fn api_on_packet(
ctx: *mut c_void,
channel: YogStr,
ud: *mut c_void,
h: YogPacketFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
handlers
.packets
.insert(channel.as_str().to_owned(), (current_registration(), ud, h));
}
}
unsafe extern "C" fn api_on_client_packet(
ctx: *mut c_void,
channel: YogStr,
ud: *mut c_void,
h: YogPacketFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
handlers
.client_packets
.insert(channel.as_str().to_owned(), (current_registration(), ud, h));
}
}
unsafe extern "C" fn api_register_command(
ctx: *mut c_void,
name: YogStr,
ud: *mut c_void,
h: YogCommandFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
handlers
.commands
.entry(name.as_str().to_owned())
.or_default()
.push((current_registration(), None, ud, h));
}
}
unsafe extern "C" fn api_register_typed_command(
ctx: *mut c_void,
name: YogStr,
schema: YogStr,
ud: *mut c_void,
h: YogCommandFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let n = name.as_str().to_owned();
handlers.commands.entry(n).or_default().push((
current_registration(),
Some(schema.as_str().to_owned()),
ud,
h,
));
}
}
unsafe extern "C" fn api_register_recipe_json(
ctx: *mut c_void,
namespace: YogStr,
name: YogStr,
json: YogStr,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let ns = namespace.as_str().to_owned();
let n = name.as_str().to_owned();
let j = json.as_str().to_owned();
let full_id = format!("{ns}:{n}");
for r in handlers.book_renderers.lock().unwrap().values_mut() {
r.add_recipe(full_id.clone(), &j);
}
handlers.recipes.push((ns, n, j));
}
}
unsafe extern "C" fn api_register_item(ctx: *mut c_void, def: *const YogItemDef) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let d = &*def;
let food = if d.food_nutrition > 0 {
Some(FoodDef {
nutrition: d.food_nutrition,
saturation: d.food_saturation,
can_always_eat: d.food_always_eat,
})
} else {
None
};
handlers.items.push(ItemDef {
id: d.id.as_str().to_owned(),
max_stack: d.max_stack as u8,
name: if d.name.is_empty() {
None
} else {
Some(d.name.as_str().to_owned())
},
tooltip: if d.tooltip.is_empty() {
None
} else {
Some(d.tooltip.as_str().to_owned())
},
max_damage: d.max_damage,
fire_resistant: d.fire_resistant,
fuel_ticks: d.fuel_ticks,
food,
});
}
}
unsafe extern "C" fn api_register_block(ctx: *mut c_void, def: *const YogBlockDef) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let d = &*def;
handlers.blocks.push(BlockDef {
id: d.id.as_str().to_owned(),
hardness: d.hardness,
resistance: d.resistance,
name: if d.name.is_empty() {
None
} else {
Some(d.name.as_str().to_owned())
},
light_level: d.light_level,
sound: if d.sound.is_empty() {
None
} else {
Some(d.sound.as_str().to_owned())
},
requires_tool: d.requires_tool,
no_collision: d.no_collision,
slipperiness: d.slipperiness,
shape: if d.shape == [0.0f32; 6] {
None
} else {
Some(d.shape)
},
connects: d.connects,
connect_groups: if d.connect_groups.is_empty() {
Vec::new()
} else {
d.connect_groups
.as_str()
.split(',')
.map(str::to_owned)
.collect()
},
inventory_id: if d.inventory_id.is_empty() {
None
} else {
Some(d.inventory_id.as_str().to_owned())
},
});
}
}
unsafe extern "C" fn api_register_inventory(
ctx: *mut c_void,
def: *const yog_abi::YogInventoryDef,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let d = &*def;
handlers.inventories.push(yog_inventory::InventoryDef {
id: d.id.as_str().to_owned(),
slot_count: d.slot_count as usize,
layout: yog_inventory::decode_layout(d.layout.as_str()),
include_player_inventory: d.include_player_inventory,
player_inv_offset: (d.player_inv_x, d.player_inv_y),
background_texture: if d.background_texture.is_empty() {
None
} else {
Some(d.background_texture.as_str().to_owned())
},
title: d.title.as_str().to_owned(),
background_size: if d.background_w > 0.0 && d.background_h > 0.0 {
Some((d.background_w, d.background_h))
} else {
None
},
});
}
}
unsafe extern "C" fn api_schedule_once(
ctx: *mut c_void,
delay_ticks: u64,
ud: *mut c_void,
h: YogScheduledFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let handlers = handlers_lock.write().expect("handlers lock poisoned");
handlers
.scheduler
.lock()
.expect("scheduler poisoned")
.once_tasks
.push(OnceTask {
module: current_registration(),
delay_remaining: delay_ticks,
ud,
f: h,
});
}
}
unsafe extern "C" fn api_schedule_repeating(
ctx: *mut c_void,
period_ticks: u64,
ud: *mut c_void,
h: YogScheduledFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let handlers = handlers_lock.write().expect("handlers lock poisoned");
handlers
.scheduler
.lock()
.expect("scheduler poisoned")
.repeating_tasks
.push(RepeatingTask {
module: current_registration(),
period: period_ticks,
ticks_left: period_ticks,
ud,
f: h,
});
}
}
unsafe extern "C" fn api_register_startup_grant(
ctx: *mut c_void,
grant: *const YogStartupGrantDef,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let g = &*grant;
let items: Vec<String> = if g.items.is_empty() {
Vec::new()
} else {
{
g.items
.as_str()
.split('|')
.map(|s: &str| s.to_owned())
.collect()
}
};
let book = if g.book.is_empty() {
None
} else {
Some(g.book.as_str().to_owned())
};
let command = if g.command.is_empty() {
None
} else {
Some(g.command.as_str().to_owned())
};
handlers.startup_grants.push(yog_registry::StartupGrant {
id: g.id.as_str().to_owned(),
items,
book,
command,
});
}
}
unsafe extern "C" fn api_register_book(ctx: *mut c_void, book_id: YogStr, book_json: YogStr) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let id = book_id.as_str().to_owned();
let json = book_json.as_str().to_owned();
handlers.books.insert(id.clone(), json.clone());
match serde_json::from_str::<yog_book::Book>(&json) {
Ok(book) => {
let mut renderer = yog_book::BookRenderer::new(book);
renderer.set_translator(translate);
for (ns, name, j) in &handlers.recipes {
renderer.add_recipe(format!("{ns}:{name}"), j);
}
handlers.book_renderers.lock().unwrap().insert(id, renderer);
}
Err(e) => {
yog_logging::warn!(
"book '{}': failed to parse JSON for Rust renderer: {}",
id,
e
);
}
}
}
}
unsafe extern "C" fn api_register_dimension_json(ctx: *mut c_void, id: YogStr, json: YogStr) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let id = id.as_str().to_owned();
let json = json.as_str().to_owned();
yog_logging::info!("registered dimension type: {}", id);
handlers.dimensions.insert(id, json);
}
}
unsafe extern "C" fn api_register_chunk_generator(
ctx: *mut c_void,
id: YogStr,
ud: *mut c_void,
h: yog_abi::YogChunkGeneratorFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let id = id.as_str().to_owned();
yog_logging::info!("registered chunk generator for dimension: {}", id);
handlers
.chunk_generators
.insert(id, (current_registration(), ud, h));
}
}
unsafe extern "C" fn api_register_ui(
ctx: *mut c_void,
ui_id: YogStr,
_layout_json: YogStr,
ud: *mut c_void,
h: yog_abi::YogUIEventFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let id = ui_id.as_str().to_owned();
handlers
.ui_handlers
.insert(id.clone(), (current_registration(), ud, h));
yog_logging::info!("registered UI handler: {}", id);
}
}
unsafe extern "C" fn api_on_ui_render(
ctx: *mut c_void,
ui_id: YogStr,
ud: *mut c_void,
h: YogHudRenderFn,
) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let id = ui_id.as_str().to_owned();
handlers
.ui_render_handlers
.entry(id)
.or_default()
.push((current_registration(), ud, h));
}
}
fn tsv_clean(v: &str) -> String {
v.replace(['\t', '\n', '\r'], " ")
}
unsafe extern "C" fn api_ui_open(_ctx: *mut c_void, ui_id: YogStr, modal: bool, pause: bool) {
unsafe {
if ui_id.as_str().is_empty() {
return;
}
let _: Option<()> = bridge!("openUI", [S(ui_id), Z(modal), Z(pause)]);
}
}
unsafe extern "C" fn api_ui_close(_ctx: *mut c_void, ui_id: YogStr) {
unsafe {
let _: Option<()> = bridge!("closeUI", [S(ui_id)]);
}
}
unsafe extern "C" fn api_ui_consume_event(_ctx: *mut c_void) {
ui_jni::consume_event();
}
unsafe extern "C" fn api_translate(_ctx: *mut c_void, key: YogStr) -> YogOwnedStr {
unsafe { translate(key.as_str()).map_or(YogOwnedStr::NONE, YogOwnedStr::from_string) }
}
fn translate(key: &str) -> Option<String> {
if key.is_empty() {
return None;
}
unsafe { bridge!("translate", [S(YogStr::from_str(key))]) }
}
unsafe extern "C" fn api_mods_list(_ctx: *mut c_void) -> YogOwnedStr {
unsafe {
let mut lines: Vec<String> = Vec::new();
let infos = MOD_INFOS.lock().expect("mod infos lock poisoned").clone();
for m in &infos {
let localized = |text: &str, key: &str| {
translate(key).map_or_else(|| text.to_string(), |t| tsv_clean(&t))
};
lines.push(format!(
"yog\t{}\t{}\t{}\t{}\t{}",
m[0],
localized(&m[1], &m[5]),
m[2],
m[3],
localized(&m[4], &m[6])
));
}
let platform: Option<String> = bridge!("listPlatformMods", []);
for line in platform.iter().flat_map(|s| s.lines()) {
if !line.trim().is_empty() {
lines.push(format!("platform\t{line}"));
}
}
YogOwnedStr::from_string(lines.join("\n"))
}
}
unsafe extern "C" fn api_register_menu_entry(ctx: *mut c_void, label: YogStr, ui_id: YogStr) {
unsafe {
let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
let l = label.as_str().to_owned();
let u = ui_id.as_str().to_owned();
handlers.menu_entries.push((l, u));
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeBookJson<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
book_id: JString<'l>,
) -> jstring {
let id = jstr!(env, book_id, std::ptr::null_mut());
let json = handlers()
.books
.get(&id)
.cloned()
.unwrap_or_else(|| "null".to_string());
env.new_string(json)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
fn vec_args(v: YogVec3) -> [JArg<'static>; 3] {
[JArg::D(v.x), JArg::D(v.y), JArg::D(v.z)]
}
fn pos_args(p: YogBlockPos) -> [JArg<'static>; 3] {
[JArg::I(p.x), JArg::I(p.y), JArg::I(p.z)]
}
unsafe extern "C" fn srv_entity_info(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("entityInfo", [JArg::S(uuid)])) }
}
unsafe extern "C" fn srv_entities_in_box(
_ctx: *mut c_void,
dim: YogStr,
min: YogVec3,
max: YogVec3,
) -> YogOwnedStr {
unsafe {
let [a, b, c] = vec_args(min);
let [d, e, f] = vec_args(max);
owned(bridge!("entitiesInBox", [JArg::S(dim), a, b, c, d, e, f]))
}
}
unsafe extern "C" fn srv_raycast_block(
_ctx: *mut c_void,
dim: YogStr,
from: YogVec3,
to: YogVec3,
) -> YogOwnedStr {
unsafe {
let [a, b, c] = vec_args(from);
let [d, e, f] = vec_args(to);
owned(bridge!("raycastBlock", [JArg::S(dim), a, b, c, d, e, f]))
}
}
unsafe extern "C" fn srv_raycast_entity(
_ctx: *mut c_void,
dim: YogStr,
from: YogVec3,
to: YogVec3,
exclude: YogStr,
) -> YogOwnedStr {
unsafe {
let [a, b, c] = vec_args(from);
let [d, e, f] = vec_args(to);
owned(bridge!(
"raycastEntity",
[JArg::S(dim), a, b, c, d, e, f, JArg::S(exclude)]
))
}
}
unsafe extern "C" fn srv_block_info(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
) -> YogOwnedStr {
unsafe {
let [x, y, z] = pos_args(pos);
owned(bridge!("blockInfo", [JArg::S(dim), x, y, z]))
}
}
unsafe extern "C" fn srv_break_block(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
drop: bool,
breaker: YogStr,
) -> bool {
unsafe {
let [x, y, z] = pos_args(pos);
bridge!(
"breakBlock",
[JArg::S(dim), x, y, z, JArg::Z(drop), JArg::S(breaker)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_place_block_from_slot(
_ctx: *mut c_void,
player: YogStr,
slot: u32,
dim: YogStr,
pos: YogBlockPos,
) -> bool {
unsafe {
let [x, y, z] = pos_args(pos);
bridge!(
"placeBlockFromSlot",
[JArg::S(player), JArg::I(slot as i32), JArg::S(dim), x, y, z]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_bonemeal(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> bool {
unsafe {
let [x, y, z] = pos_args(pos);
bridge!("bonemeal", [JArg::S(dim), x, y, z]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_explode(
_ctx: *mut c_void,
dim: YogStr,
pos: YogVec3,
power: f32,
fire: bool,
source: YogStr,
) -> bool {
unsafe {
let [x, y, z] = vec_args(pos);
bridge!(
"explode",
[
JArg::S(dim),
x,
y,
z,
JArg::F(power),
JArg::Z(fire),
JArg::S(source),
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_tellraw(_ctx: *mut c_void, player: YogStr, json: YogStr) -> bool {
unsafe { bridge!("tellraw", [JArg::S(player), JArg::S(json)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_entity_damage(
_ctx: *mut c_void,
uuid: YogStr,
amount: f32,
damage_type: YogStr,
attacker: YogStr,
) -> bool {
unsafe {
bridge!(
"entityDamage",
[
JArg::S(uuid),
JArg::F(amount),
JArg::S(damage_type),
JArg::S(attacker),
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_entity_set_fire_ticks(
_ctx: *mut c_void,
uuid: YogStr,
ticks: i32,
) -> bool {
unsafe { bridge!("entitySetFireTicks", [JArg::S(uuid), JArg::I(ticks)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_player_add_xp(_ctx: *mut c_void, player: YogStr, amount: i32) -> bool {
unsafe { bridge!("playerAddXp", [JArg::S(player), JArg::I(amount)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_player_set_may_fly(
_ctx: *mut c_void,
player: YogStr,
may_fly: bool,
) -> bool {
unsafe { bridge!("playerSetMayFly", [JArg::S(player), JArg::Z(may_fly)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_player_info(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("playerInfo", [JArg::S(player)])) }
}
unsafe extern "C" fn srv_spawn_item(
_ctx: *mut c_void,
dim: YogStr,
pos: YogVec3,
vel: YogVec3,
item_id: YogStr,
count: u32,
snbt: YogStr,
) -> YogOwnedStr {
unsafe {
let [a, b, c] = vec_args(pos);
let [d, e, f] = vec_args(vel);
owned(bridge!(
"spawnItem",
[
JArg::S(dim),
a,
b,
c,
d,
e,
f,
JArg::S(item_id),
JArg::I(count as i32),
JArg::S(snbt),
]
))
}
}
unsafe extern "C" fn srv_item_entity_stack(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("itemEntityStack", [JArg::S(uuid)])) }
}
unsafe extern "C" fn srv_actionbar_json(_ctx: *mut c_void, player: YogStr, json: YogStr) -> bool {
unsafe { bridge!("actionbarJson", [JArg::S(player), JArg::S(json)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_use_item_on_block(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
face: u8,
item_id: YogStr,
player: YogStr,
) -> bool {
unsafe {
let [x, y, z] = pos_args(pos);
bridge!(
"useItemOnBlock",
[
JArg::S(dim),
x,
y,
z,
JArg::I(face as i32),
JArg::S(item_id),
JArg::S(player),
]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_pour_fluid(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
bucket_item_id: YogStr,
) -> bool {
unsafe {
let [x, y, z] = pos_args(pos);
bridge!(
"pourFluid",
[JArg::S(dim), x, y, z, JArg::S(bucket_item_id)]
)
.unwrap_or(false)
}
}
unsafe extern "C" fn srv_has_entity_tag(
_ctx: *mut c_void,
entity_type: YogStr,
tag: YogStr,
) -> bool {
unsafe { bridge!("hasEntityTag", [JArg::S(entity_type), JArg::S(tag)]).unwrap_or(false) }
}
unsafe extern "C" fn srv_item_block(_ctx: *mut c_void, item_id: YogStr) -> YogOwnedStr {
unsafe { owned(bridge!("itemBlock", [JArg::S(item_id)])) }
}
unsafe extern "C" fn srv_place_feature(
_ctx: *mut c_void,
dim: YogStr,
pos: YogBlockPos,
feature_id: YogStr,
) -> bool {
unsafe {
let [x, y, z] = pos_args(pos);
bridge!("placeFeature", [JArg::S(dim), x, y, z, JArg::S(feature_id)]).unwrap_or(false)
}
}
unsafe extern "C" fn srv_item_entity_set_stack(
_ctx: *mut c_void,
uuid: YogStr,
item_id: YogStr,
count: u32,
snbt: YogStr,
) -> bool {
unsafe {
bridge!(
"itemEntitySetStack",
[
JArg::S(uuid),
JArg::S(item_id),
JArg::I(count as i32),
JArg::S(snbt),
]
)
.unwrap_or(false)
}
}
fn build_server_table() -> YogServer {
YogServer {
ctx: std::ptr::null_mut(),
abi_version: ABI_VERSION,
size: std::mem::size_of::<YogServer>() as u32,
free_str: yog_free_str,
broadcast: srv_broadcast,
get_block: srv_get_block,
set_block: srv_set_block,
world_time: srv_world_time,
set_time: srv_set_time,
is_raining: srv_is_raining,
set_weather: srv_set_weather,
give_item: srv_give_item,
player_teleport: srv_player_teleport,
send_to_player: srv_send_to_player,
send_to_server: srv_send_to_server,
kick_player: srv_kick_player,
set_gamemode: srv_set_gamemode,
send_title: srv_send_title,
send_actionbar: srv_send_actionbar,
play_sound: srv_play_sound,
play_sound_player: srv_play_sound_player,
entity_teleport: srv_entity_teleport,
entity_position: srv_entity_position,
entity_rotation: srv_entity_rotation,
entity_health: srv_entity_health,
entity_set_health: srv_entity_set_health,
entity_kill: srv_entity_kill,
spawn_entity: srv_spawn_entity,
entity_add_effect: srv_entity_add_effect,
entity_remove_effect: srv_entity_remove_effect,
entity_clear_effects: srv_entity_clear_effects,
entity_velocity: srv_entity_velocity,
entity_set_velocity: srv_entity_set_velocity,
entity_add_velocity: srv_entity_add_velocity,
has_item_tag: srv_has_item_tag,
has_block_tag: srv_has_block_tag,
drop_loot: srv_drop_loot,
scoreboard_get: srv_scoreboard_get,
scoreboard_set: srv_scoreboard_set,
scoreboard_add: srv_scoreboard_add,
bossbar_create: srv_bossbar_create,
bossbar_remove: srv_bossbar_remove,
bossbar_set_title: srv_bossbar_set_title,
bossbar_set_progress: srv_bossbar_set_progress,
bossbar_set_color: srv_bossbar_set_color,
bossbar_add_player: srv_bossbar_add_player,
bossbar_remove_player: srv_bossbar_remove_player,
bossbar_set_visible: srv_bossbar_set_visible,
game_dir: srv_game_dir,
get_block_nbt: srv_get_block_nbt,
set_block_nbt: srv_set_block_nbt,
get_inventory_slot: srv_get_inventory_slot,
set_inventory_slot: srv_set_inventory_slot,
player_inventory: srv_player_inventory,
player_set_slot: srv_player_set_slot,
player_teleport_dim: srv_player_teleport_dim,
entity_teleport_dim: srv_entity_teleport_dim,
online_players: srv_online_players,
world_entity_count: srv_world_entity_count,
entity_get_nbt: srv_entity_get_nbt,
entity_set_nbt: srv_entity_set_nbt,
spawn_particles: srv_spawn_particles,
entity_attribute_get: srv_entity_attribute_get,
entity_attribute_set: srv_entity_attribute_set,
get_held_item_nbt: srv_get_held_item_nbt,
set_held_item_nbt: srv_set_held_item_nbt,
get_offhand_item_nbt: srv_get_offhand_item_nbt,
set_offhand_item_nbt: srv_set_offhand_item_nbt,
get_slot_item: srv_get_slot_item,
set_slot_item: srv_set_slot_item,
entity_info: srv_entity_info,
entities_in_box: srv_entities_in_box,
raycast_block: srv_raycast_block,
raycast_entity: srv_raycast_entity,
block_info: srv_block_info,
break_block: srv_break_block,
place_block_from_slot: srv_place_block_from_slot,
bonemeal: srv_bonemeal,
explode: srv_explode,
tellraw: srv_tellraw,
entity_damage: srv_entity_damage,
entity_set_fire_ticks: srv_entity_set_fire_ticks,
player_add_xp: srv_player_add_xp,
player_set_may_fly: srv_player_set_may_fly,
player_info: srv_player_info,
spawn_item: srv_spawn_item,
item_entity_stack: srv_item_entity_stack,
item_entity_set_stack: srv_item_entity_set_stack,
actionbar_json: srv_actionbar_json,
use_item_on_block: srv_use_item_on_block,
pour_fluid: srv_pour_fluid,
has_entity_tag: srv_has_entity_tag,
item_block: srv_item_block,
place_feature: srv_place_feature,
}
}
unsafe extern "C" fn interop_export_impl(
_ctx: *mut c_void,
mod_id: YogStr,
symbol: YogStr,
ptr: *const c_void,
) {
unsafe {
let mod_id = mod_id.as_str();
let sym = symbol.as_str();
let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
registry
.lock()
.expect("interop symbols lock poisoned")
.entry(mod_id.to_string())
.or_default()
.insert(sym.to_string(), ptr as usize);
}
}
unsafe extern "C" fn interop_import_impl(
_ctx: *mut c_void,
mod_id: YogStr,
symbol: YogStr,
) -> *const c_void {
unsafe {
let mod_id = mod_id.as_str();
let sym = symbol.as_str();
let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
registry
.lock()
.expect("interop symbols lock poisoned")
.get(mod_id)
.and_then(|m: &HashMap<String, usize>| m.get(sym))
.map(|&ptr| ptr as *const c_void)
.unwrap_or(std::ptr::null())
}
}
fn build_api_table(ctx: *const RwLock<RuntimeHandlers>, server: *const YogServer) -> YogApi {
YogApi {
abi_version: ABI_VERSION,
size: std::mem::size_of::<YogApi>() as u32,
ctx: ctx as *mut c_void,
server,
on_block_break: api_on_block_break,
on_chat: api_on_chat,
on_player_join: api_on_player_join,
on_player_leave: api_on_player_leave,
on_use_item: api_on_use_item,
on_use_block: api_on_use_block,
on_attack_entity: api_on_attack_entity,
on_entity_damage: api_on_entity_damage,
on_entity_death: api_on_entity_death,
on_entity_spawn: api_on_entity_spawn,
on_player_place_block: api_on_player_place_block,
on_player_death: api_on_player_death,
on_player_respawn: api_on_player_respawn,
on_advancement: api_on_advancement,
on_entity_interact: api_on_entity_interact,
on_item_craft: api_on_item_craft,
on_explosion: api_on_explosion,
on_item_pickup: api_on_item_pickup,
on_player_move: api_on_player_move,
on_container_open: api_on_container_open,
on_container_close: api_on_container_close,
on_projectile_hit: api_on_projectile_hit,
on_server_tick: api_on_server_tick,
on_server_started: api_on_server_started,
on_server_stopping: api_on_server_stopping,
on_packet: api_on_packet,
on_client_packet: api_on_client_packet,
register_command: api_register_command,
register_typed_command: api_register_typed_command,
register_recipe_json: api_register_recipe_json,
register_item: api_register_item,
register_block: api_register_block,
schedule_once: api_schedule_once,
schedule_repeating: api_schedule_repeating,
on_client_tick: api_on_client_tick,
on_hud_render: api_on_hud_render,
on_key_press: api_on_key_press,
on_screen_open: api_on_screen_open,
on_screen_close: api_on_screen_close,
on_world_render: api_on_world_render,
register_startup_grant: api_register_startup_grant,
register_book: api_register_book,
register_ui: api_register_ui,
on_ui_render: api_on_ui_render,
register_menu_entry: api_register_menu_entry,
mods_list: api_mods_list,
free_str: yog_free_str,
ui_open: api_ui_open,
register_inventory: api_register_inventory,
interop_export: interop_export_impl,
interop_import: interop_import_impl,
register_dimension_json: api_register_dimension_json,
register_chunk_generator: api_register_chunk_generator,
ui_close: api_ui_close,
ui_consume_event: api_ui_consume_event,
translate: api_translate,
ui_open_flags: client_api::api_ui_open_flags,
client_play_sound: client_api::api_client_play_sound,
client_stop_sound: client_api::api_client_stop_sound,
client_held_item: client_api::api_client_held_item,
client_key_down: client_api::api_client_key_down,
}
}
fn platform_tag() -> String {
format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
}
type AbiVersionFn = unsafe extern "C" fn() -> u32;
type RegisterFn = unsafe extern "C" fn(*const YogApi, *const std::os::raw::c_char);
fn load_mods(dir: &Path, api: &YogApi) {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => {
yog_logging::info!("no mods directory at {} — none loaded", dir.display());
return;
}
};
struct ModMeta {
id: String,
deps: Vec<String>,
yog_path: PathBuf,
}
let mut metas: Vec<ModMeta> = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("yog") {
continue;
}
let info = read_mod_info(&path);
let deps = read_mod_deps(&path);
metas.push(ModMeta {
id: info[0].clone(),
deps,
yog_path: path,
});
}
let ids: Vec<String> = metas.iter().map(|m| m.id.clone()).collect();
let deps_list: Vec<Vec<String>> = metas.iter().map(|m| m.deps.clone()).collect();
let sorted = match topo_sort_mods(&ids, &deps_list) {
Some(v) => v,
None => {
yog_logging::error!("circular dependency detected — loading in file order");
(0..metas.len()).collect()
}
};
let mut loaded: Vec<LoadedModule> = Vec::new();
for idx in sorted {
let meta = &metas[idx];
let lib_path = match extract_yog(&meta.yog_path) {
Some(p) => p,
None => {
yog_logging::error!(
"no native for {} in {}",
platform_tag(),
meta.yog_path.display()
);
continue;
}
};
if let Some(module) = load_mod_lib(&lib_path, api, &meta.id) {
loaded.push(module);
MOD_INFOS
.lock()
.expect("mod infos lock poisoned")
.push(read_mod_info(&meta.yog_path));
}
}
yog_logging::info!("loaded {} mod(s) from {}", loaded.len(), dir.display());
resolve_all_imports(api, &loaded);
}
fn resolve_all_imports(api: &YogApi, loaded: &[LoadedModule]) {
for module in loaded {
module.with_library(|lib| unsafe {
type ResolveFn = unsafe extern "C" fn(*const YogApi);
if let Ok(resolve) = lib.get::<ResolveFn>(b"__yog_resolve_imports_final") {
resolve(api as *const YogApi);
}
});
}
}
fn topo_sort_mods(ids: &[String], deps_list: &[Vec<String>]) -> Option<Vec<usize>> {
let n = ids.len();
let name_to_idx: HashMap<&str, usize> = ids
.iter()
.enumerate()
.map(|(i, id)| (id.as_str(), i))
.collect();
let mut in_degree = vec![0u32; n];
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for (i, deps) in deps_list.iter().enumerate() {
for dep in deps {
if let Some(&j) = name_to_idx.get(dep.as_str()) {
adj[j].push(i);
in_degree[i] += 1;
}
}
}
let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
let mut order = Vec::with_capacity(n);
while let Some(u) = queue.pop() {
order.push(u);
for &v in &adj[u] {
in_degree[v] -= 1;
if in_degree[v] == 0 {
queue.push(v);
}
}
}
if order.len() == n { Some(order) } else { None }
}
fn load_mod_lib(path: &Path, api: &YogApi, mod_id: &str) -> Option<LoadedModule> {
let module = match unsafe { hot_reloader().load_initial(mod_id, path) } {
Ok(m) => m,
Err(e) => {
yog_logging::error!("failed to load {}: {}", path.display(), e);
return None;
}
};
let abi = module.with_library(|lib| unsafe {
let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
Some(abi())
});
match abi.flatten() {
Some(v) if v == ABI_VERSION => {}
Some(v) => {
yog_logging::error!(
"{}: ABI {} incompatible with runtime ABI {}",
path.display(),
v,
ABI_VERSION
);
module.retire(|| {});
return None;
}
None => {
yog_logging::error!("{} is not a Yog mod (no yog_abi_version)", path.display());
module.retire(|| {});
return None;
}
}
if let Err(e) = invoke_mod_register(&module, api, mod_id) {
yog_logging::error!("{}: {}", path.display(), e);
module.retire(|| {});
return None;
}
Some(module)
}
fn invoke_mod_register(module: &LoadedModule, api: &YogApi, mod_id: &str) -> Result<(), String> {
module
.with_library(|lib| unsafe {
let register: Symbol<RegisterFn> = lib
.get(b"yog_mod_register")
.map_err(|_| "missing yog_mod_register".to_string())?;
let mod_id_c = std::ffi::CString::new(mod_id).unwrap();
*CURRENT_REGISTRATION.lock().unwrap() = Some(module.clone());
register(api as *const YogApi, mod_id_c.as_ptr());
*CURRENT_REGISTRATION.lock().unwrap() = None;
Ok(())
})
.unwrap_or_else(|| Err("module was unloaded before registration could run".to_string()))
}
fn read_mod_info(path: &Path) -> [String; 7] {
let stem = path
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
let mut info = [
stem.clone(),
stem,
String::new(),
String::new(),
String::new(),
String::new(),
String::new(),
];
if path.extension().and_then(|e| e.to_str()) != Some("yog") {
return info;
}
let Some(text) = (|| {
let file = std::fs::File::open(path).ok()?;
let mut archive = zip::ZipArchive::new(file).ok()?;
let mut entry = archive.by_name("yog.toml").ok()?;
let mut text = String::new();
std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
Some(text)
})() else {
return info;
};
let mut in_mod = true;
for line in text.lines() {
let line = line.trim();
if line.starts_with('[') {
in_mod = line == "[mod]";
continue;
}
if !in_mod {
continue;
}
let Some((key, value)) = line.split_once('=') else {
continue;
};
let key = key.trim();
let value = value.trim();
let unquote = |v: &str| v.trim().trim_matches('"').to_string();
match key {
"id" => info[0] = tsv_clean(&unquote(value)),
"name" => info[1] = tsv_clean(&unquote(value)),
"version" => info[2] = tsv_clean(&unquote(value)),
"description" => info[4] = tsv_clean(&unquote(value)),
"name_key" => info[5] = unquote(value),
"description_key" => info[6] = unquote(value),
"authors" => {
let list = value.trim_start_matches('[').trim_end_matches(']');
let authors: Vec<String> = list
.split(',')
.map(unquote)
.filter(|a| !a.is_empty())
.collect();
info[3] = tsv_clean(&authors.join(", "));
}
_ => {}
}
}
info
}
fn read_mod_deps(path: &Path) -> Vec<String> {
if path.extension().and_then(|e| e.to_str()) != Some("yog") {
return Vec::new();
}
let text = (|| {
let file = std::fs::File::open(path).ok()?;
let mut archive = zip::ZipArchive::new(file).ok()?;
let mut entry = archive.by_name("yog.toml").ok()?;
let mut text = String::new();
std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
Some(text)
})();
let Some(text) = text else { return Vec::new() };
let mut deps = Vec::new();
let mut in_deps = false;
for line in text.lines() {
let line = line.trim();
if line.starts_with('[') {
in_deps = line == "[dependencies]";
continue;
}
if !in_deps {
continue;
}
if let Some((key, _)) = line.split_once('=') {
deps.push(key.trim().to_string());
}
}
deps
}
fn extract_yog(path: &Path) -> Option<PathBuf> {
let file = std::fs::File::open(path).ok()?;
let mut archive = zip::ZipArchive::new(file).ok()?;
let prefix = format!("natives/{}/", platform_tag());
let mut entry_name = None;
for i in 0..archive.len() {
let f = archive.by_index(i).ok()?;
let name = f.name().ok()?;
if name.starts_with(&prefix) && !name.ends_with('/') {
entry_name = Some(name.to_string());
break;
}
}
let entry_name = entry_name?;
let ext = Path::new(&entry_name)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("bin");
let stem = path.file_stem()?.to_string_lossy().into_owned();
static EXTRACTIONS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let n = EXTRACTIONS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let out =
std::env::temp_dir().join(format!("yog-{}-{}-{}.{}", stem, std::process::id(), n, ext));
let mut entry = archive.by_name(&entry_name).ok()?;
let mut out_file = std::fs::File::create(&out).ok()?;
std::io::copy(&mut entry, &mut out_file).ok()?;
Some(out)
}
fn srv_ptr() -> *const YogServer {
SERVER.get().expect("yog: SERVER not initialised") as *const YogServer
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeInit<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
mods_dir: JString<'l>,
) {
if let Ok(vm) = env.get_java_vm() {
let _ = JAVA_VM.set(vm);
}
let dir = env
.get_string(&mods_dir)
.map(String::from)
.unwrap_or_default();
let _ = SERVER.set(build_server_table());
let server_ptr = SERVER.get().unwrap() as *const YogServer;
let handlers_lock = HANDLERS.get_or_init(|| RwLock::new(RuntimeHandlers::default()));
let handlers_ptr = handlers_lock as *const RwLock<RuntimeHandlers>;
let api: &'static YogApi = API_TABLE.get_or_init(|| build_api_table(handlers_ptr, server_ptr));
let _ = HOT_RELOADER.get_or_init(|| HotReloader::new(RuntimeModuleRegistry));
guard("mod loading", || {
load_mods(Path::new(&dir), api);
});
control_socket::start_if_requested();
yog_logging::info!("runtime initialised — the gate is open.");
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeHotReload<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
mod_id: JString<'l>,
new_yog_path: JString<'l>,
) -> jni::sys::jboolean {
let Ok(mod_id) = env.get_string(&mod_id).map(String::from) else {
return 0;
};
let Ok(yog_path) = env.get_string(&new_yog_path).map(String::from) else {
return 0;
};
let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
trigger_hot_reload(&mod_id, Path::new(&yog_path))
}))
.unwrap_or(false);
ok as jni::sys::jboolean
}
fn trigger_hot_reload(mod_id: &str, new_yog_path: &Path) -> bool {
let Some(native_path) = extract_yog(new_yog_path) else {
yog_logging::error!(
"hot-reload {}: no native for {} in {}",
mod_id,
platform_tag(),
new_yog_path.display()
);
return false;
};
let api = API_TABLE.get().expect("yog: nativeInit not called yet");
let module = match unsafe { hot_reloader().reload(mod_id, &native_path) } {
Ok(m) => m,
Err(e) => {
yog_logging::error!(
"hot-reload {}: failed to load {}: {}",
mod_id,
native_path.display(),
e
);
return false;
}
};
let abi = module.with_library(|lib| unsafe {
let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
Some(abi())
});
match abi.flatten() {
Some(v) if v == ABI_VERSION => {}
other => {
yog_logging::error!(
"hot-reload {}: ABI {:?} incompatible with runtime ABI {} — mod has no active handlers until a compatible reload succeeds",
mod_id,
other,
ABI_VERSION
);
return false;
}
}
if let Err(e) = invoke_mod_register(&module, api, mod_id) {
yog_logging::error!("hot-reload {}: {}", mod_id, e);
return false;
}
yog_logging::info!("hot-reloaded mod {}", mod_id);
true
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreak<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
block: JString<'l>,
x: jint,
y: jint,
z: jint,
dimension: JString<'l>,
) {
let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
let ev = yog_abi::YogBlockBreakEvent {
player: YogStr::from_str(&p),
block: YogStr::from_str(&b),
pos: YogBlockPos { x, y, z },
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_block_break", || {
each(&handlers().block_break, |ud, f| unsafe {
f(ud, srv, &ev, 1)
});
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChat<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
message: JString<'l>,
dimension: JString<'l>,
) {
let (p, m, d) = (
jstr!(env, player),
jstr!(env, message),
jstr!(env, dimension),
);
let ev = yog_abi::YogChatEvent {
player: YogStr::from_str(&p),
message: YogStr::from_str(&m),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_chat", || {
each(&handlers().chat, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerJoin<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
uuid: JString<'l>,
dimension: JString<'l>,
) {
let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
let ev = yog_abi::YogPlayerEvent {
player: YogStr::from_str(&p),
uuid: YogStr::from_str(&u),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_player_join", || {
each(&handlers().player_join, |ud, f| unsafe {
f(ud, srv, &ev, 1)
});
});
let h = handlers();
if !h.startup_grants.is_empty() {
h.pending_grants
.lock()
.expect("pending_grants poisoned")
.push((p, u, 0));
}
}
fn process_pending_grants() {
let h = handlers();
let mut pending = match h.pending_grants.try_lock() {
Ok(g) => g,
Err(_) => return,
};
if pending.is_empty() {
return;
}
const FIRST_TRY_TICK: u32 = 2;
const MAX_TICKS: u32 = 600;
let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
let mut still_pending = Vec::new();
let mut any_new = false;
for (p, u, ticks) in pending.drain(..) {
if ticks < FIRST_TRY_TICK {
still_pending.push((p, u, ticks + 1));
continue;
}
let mut all_ok = true;
for sg in &h.startup_grants {
let key = format!("{}::{}", u, sg.id);
if granted.contains_key(&key) {
continue;
}
let mut ok = true;
for item_id in &sg.items {
ok &= unsafe {
srv_give_item(
std::ptr::null_mut(),
YogStr::from_str(&p),
YogStr::from_str(item_id),
1,
)
};
}
if let Some(_book) = &sg.book {
ok &= unsafe {
srv_give_item(
std::ptr::null_mut(),
YogStr::from_str(&p),
YogStr::from_str("minecraft:written_book"),
1,
)
};
}
if ok {
yog_logging::info!("startup grant {} given to {}", sg.id, p);
granted.insert(key, true);
any_new = true;
} else {
all_ok = false;
}
}
if !all_ok && ticks < MAX_TICKS {
still_pending.push((p, u, ticks + 1));
}
}
*pending = still_pending;
if any_new {
save_startup_granted(&granted);
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerLeave<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
uuid: JString<'l>,
dimension: JString<'l>,
) {
let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
let ev = yog_abi::YogPlayerEvent {
player: YogStr::from_str(&p),
uuid: YogStr::from_str(&u),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_player_leave", || {
each(&handlers().player_leave, |ud, f| unsafe {
f(ud, srv, &ev, 1)
});
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItem<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
item: JString<'l>,
sneaking: jni::sys::jboolean,
dimension: JString<'l>,
) {
let (p, i, d) = (jstr!(env, player), jstr!(env, item), jstr!(env, dimension));
let ev = yog_abi::YogUseItemEvent {
player: YogStr::from_str(&p),
item: YogStr::from_str(&i),
sneaking: sneaking != 0,
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_use_item", || {
each(&handlers().use_item, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItemPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
item: JString<'l>,
sneaking: jni::sys::jboolean,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.use_item.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let i = jstr!(env, item, 1);
let d = jstr!(env, dimension, 1);
let ev = yog_abi::YogUseItemEvent {
player: YogStr::from_str(&p),
item: YogStr::from_str(&i),
sneaking: sneaking != 0,
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.use_item, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlock<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
block: JString<'l>,
x: jint,
y: jint,
z: jint,
dimension: JString<'l>,
) {
let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
let ev = yog_abi::YogUseBlockEvent {
player: YogStr::from_str(&p),
block: YogStr::from_str(&b),
pos: YogBlockPos { x, y, z },
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_use_block", || {
each(&handlers().use_block, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlockPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
block: JString<'l>,
x: jint,
y: jint,
z: jint,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.use_block.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let b = jstr!(env, block, 1);
let d = jstr!(env, dimension, 1);
let ev = yog_abi::YogUseBlockEvent {
player: YogStr::from_str(&p),
block: YogStr::from_str(&b),
pos: YogBlockPos { x, y, z },
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.use_block, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAttackEntity<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
target_type: JString<'l>,
target_uuid: JString<'l>,
dimension: JString<'l>,
) {
let (p, tt, tu, d) = (
jstr!(env, player),
jstr!(env, target_type),
jstr!(env, target_uuid),
jstr!(env, dimension),
);
let ev = yog_abi::YogAttackEntityEvent {
player: YogStr::from_str(&p),
target_type: YogStr::from_str(&tt),
target_uuid: YogStr::from_str(&tu),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_attack_entity", || {
each(&handlers().attack_entity, |ud, f| unsafe {
f(ud, srv, &ev, 1)
});
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamage<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
entity_type: JString<'l>,
uuid: JString<'l>,
amount: jfloat,
source: JString<'l>,
dimension: JString<'l>,
) {
let (et, u, s, d) = (
jstr!(env, entity_type),
jstr!(env, uuid),
jstr!(env, source),
jstr!(env, dimension),
);
let ev = yog_abi::YogEntityDamageEvent {
entity_type: YogStr::from_str(&et),
uuid: YogStr::from_str(&u),
amount,
source: YogStr::from_str(&s),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_entity_damage", || {
each(&handlers().entity_damage, |ud, f| unsafe {
f(ud, srv, &ev, 1)
});
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDeath<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
entity_type: JString<'l>,
uuid: JString<'l>,
source: JString<'l>,
dimension: JString<'l>,
) {
let (et, u, s, d) = (
jstr!(env, entity_type),
jstr!(env, uuid),
jstr!(env, source),
jstr!(env, dimension),
);
let ev = yog_abi::YogEntityDeathEvent {
entity_type: YogStr::from_str(&et),
uuid: YogStr::from_str(&u),
source: YogStr::from_str(&s),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_entity_death", || {
each(&handlers().entity_death, |ud, f| unsafe {
f(ud, srv, &ev, 1)
});
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnTick<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
) {
let h = handlers();
let srv = srv_ptr();
guard("on_tick", || {
each(&h.server_tick, |ud, f| unsafe { f(ud, srv) });
});
guard("startup_grants", process_pending_grants);
{
let mut sched = h.scheduler.lock().expect("scheduler poisoned");
let mut to_fire: Vec<(LoadedModule, *mut c_void, YogScheduledFn)> = Vec::new();
let mut remaining = Vec::new();
for task in sched.once_tasks.drain(..) {
if task.delay_remaining == 0 {
to_fire.push((task.module.clone(), task.ud, task.f));
} else {
remaining.push(OnceTask {
delay_remaining: task.delay_remaining - 1,
..task
});
}
}
sched.once_tasks = remaining;
drop(sched);
for (module, ud, f) in to_fire {
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
guard("schedule_once", || unsafe { f(ud, srv) });
}
}
{
let mut sched = h.scheduler.lock().expect("scheduler poisoned");
let mut to_fire: Vec<(LoadedModule, *mut c_void, YogScheduledFn)> = Vec::new();
for task in &mut sched.repeating_tasks {
if task.ticks_left == 0 {
to_fire.push((task.module.clone(), task.ud, task.f));
task.ticks_left = task.period;
} else {
task.ticks_left -= 1;
}
}
drop(sched);
for (module, ud, f) in to_fire {
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
guard("schedule_repeating", || unsafe { f(ud, srv) });
}
}
}
static WORLD_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
fn startup_grants_path() -> std::path::PathBuf {
let base = WORLD_DIR
.lock()
.ok()
.and_then(|d| d.clone())
.unwrap_or_else(|| std::path::PathBuf::from("."));
base.join("yog-data").join("startup_grants.json")
}
fn load_startup_granted() {
let h = handlers();
let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
granted.clear();
h.pending_grants
.lock()
.expect("pending_grants poisoned")
.clear();
let path = startup_grants_path();
let Ok(data) = std::fs::read_to_string(&path) else {
return;
};
let Ok(keys) = serde_json::from_str::<Vec<String>>(&data) else {
yog_logging::warn!("startup_grants.json: invalid JSON, ignoring");
return;
};
for k in keys {
granted.insert(k, true);
}
yog_logging::info!("loaded {} startup grants from disk", granted.len());
}
fn save_startup_granted(granted: &HashMap<String, bool>) {
let path = startup_grants_path();
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let keys: Vec<&String> = granted.keys().collect();
match serde_json::to_string(&keys) {
Ok(json) => {
let _ = std::fs::write(&path, json);
}
Err(e) => yog_logging::error!("failed to save startup_grants.json: {}", e),
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStarted<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
world_dir: JString<'l>,
) {
let dir = jstr!(env, world_dir);
*WORLD_DIR.lock().expect("WORLD_DIR poisoned") = if dir.is_empty() {
None
} else {
Some(std::path::PathBuf::from(dir))
};
load_startup_granted();
let srv = srv_ptr();
guard("on_server_started", || {
each(&handlers().server_started, |ud, f| unsafe { f(ud, srv) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStopping<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
) {
let srv = srv_ptr();
guard("on_server_stopping", || {
each(&handlers().server_stopping, |ud, f| unsafe { f(ud, srv) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeCommandNames<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let names = handlers()
.commands
.keys()
.cloned()
.collect::<Vec<_>>()
.join("\n");
env.new_string(names)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreakPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
block: JString<'l>,
x: jint,
y: jint,
z: jint,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.block_break.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let b = jstr!(env, block, 1);
let d = jstr!(env, dimension, 1);
let ev = yog_abi::YogBlockBreakEvent {
player: YogStr::from_str(&p),
block: YogStr::from_str(&b),
pos: YogBlockPos { x, y, z },
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.block_break, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChatPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
message: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.chat.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let m = jstr!(env, message, 1);
let d = jstr!(env, dimension, 1);
let ev = yog_abi::YogChatEvent {
player: YogStr::from_str(&p),
message: YogStr::from_str(&m),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.chat, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeRecipeJsons<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.recipes
.iter()
.map(|(ns, name, json)| format!("{ns}\t{name}\t{json}"))
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeDimensionJsons<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.dimensions
.iter()
.map(|(id, json)| format!("{id}\t{json}"))
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
unsafe extern "C" fn chunk_writer_set_block(
_ctx: *mut c_void,
x: i32,
y: i32,
z: i32,
block_id: YogStr,
) -> bool {
unsafe {
host!(
"dev/yog/YogCallbackChunkGenerator",
"setBlockInGeneratingChunk",
[I(x), I(y), I(z), S(block_id)]
)
.unwrap_or(false)
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeGenerateChunk<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
generator_type_id: JString<'l>,
chunk_x: jint,
chunk_z: jint,
min_y: jint,
height: jint,
) {
let id: String = match env.get_string(&generator_type_id) {
Ok(s) => String::from(s),
Err(_) => return,
};
let Some((module, ud, cb)) = handlers().chunk_generators.get(&id).cloned() else {
return;
};
if module.is_retiring() {
return;
}
let Some(_call) = module.enter() else { return };
let writer = yog_abi::YogChunkWriterApi {
chunk_x,
chunk_z,
min_y,
height,
ctx: std::ptr::null_mut(),
set_block: chunk_writer_set_block,
};
guard("chunk_generator", || unsafe {
cb(ud, &writer as *const _);
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeTypedCommandSchemas<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let mut lines = Vec::new();
for (name, regs) in handlers().commands.iter() {
let mut seen = std::collections::HashSet::new();
for (_module, schema, _, _) in regs {
if let Some(schema) = schema
&& seen.insert(schema.as_str())
{
lines.push(format!("{name}\t{schema}"));
}
}
}
env.new_string(lines.join("\n"))
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnCommand<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
name: JString<'l>,
args: JString<'l>,
source: JString<'l>,
uuid: JString<'l>,
dimension: JString<'l>,
) -> jstring {
let (n, a, s, u, d) = (
env.get_string(&name).map(String::from).unwrap_or_default(),
env.get_string(&args).map(String::from).unwrap_or_default(),
env.get_string(&source)
.map(String::from)
.unwrap_or_default(),
env.get_string(&uuid).map(String::from).unwrap_or_default(),
env.get_string(&dimension)
.map(String::from)
.unwrap_or_default(),
);
let ev = yog_abi::YogCommandEvent {
name: YogStr::from_str(&n),
args: YogStr::from_str(&a),
source: YogStr::from_str(&s),
uuid: YogStr::from_str(&u),
dimension: YogStr::from_str(&d),
};
let h = handlers();
let srv = srv_ptr();
let actual_argc = if a.is_empty() {
0
} else {
a.split('\t').count()
};
let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let Some(regs) = h.commands.get(&n) else {
return String::new();
};
for (module, schema, ud, f) in regs {
let expected_argc = schema.as_ref().map_or(0, |s| s.split_whitespace().count());
if expected_argc != actual_argc {
continue;
}
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
let mut buf = [0u8; 4096];
let mut reply_len: u32 = 0;
unsafe {
f(
*ud,
srv,
&ev,
buf.as_mut_ptr(),
buf.len() as u32,
&mut reply_len,
);
};
if reply_len > 0 {
return String::from_utf8_lossy(&buf[..reply_len as usize]).into_owned();
}
}
String::new()
}))
.unwrap_or_else(|_| {
yog_logging::error!("a mod panicked handling command `{}`", n);
String::new()
});
env.new_string(reply)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeItemDefs<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.items
.iter()
.map(|d| {
let mut parts = vec![d.id.clone()];
parts.push(format!("max_stack={}", d.max_stack));
if let Some(n) = &d.name {
parts.push(format!("name={n}"));
}
if let Some(t) = &d.tooltip {
parts.push(format!("tooltip={t}"));
}
if d.max_damage > 0 {
parts.push(format!("max_damage={}", d.max_damage));
}
if d.fire_resistant {
parts.push("fire_resistant=1".into());
}
if d.fuel_ticks > 0 {
parts.push(format!("fuel_ticks={}", d.fuel_ticks));
}
if let Some(f) = &d.food {
parts.push(format!(
"food={}:{}:{}",
f.nutrition,
f.saturation,
if f.can_always_eat { 1 } else { 0 }
));
}
parts.join("\t")
})
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeBlockDefs<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.blocks
.iter()
.map(|d| {
let mut parts = vec![d.id.clone()];
parts.push(format!("hardness={}", d.hardness));
parts.push(format!("resistance={}", d.resistance));
if let Some(n) = &d.name {
parts.push(format!("name={n}"));
}
if let Some(sh) = d.shape {
parts.push(format!(
"shape={}:{}:{}:{}:{}:{}",
sh[0], sh[1], sh[2], sh[3], sh[4], sh[5]
));
}
if d.light_level > 0 {
parts.push(format!("light={}", d.light_level));
}
if let Some(snd) = &d.sound {
parts.push(format!("sound={snd}"));
}
if d.requires_tool {
parts.push("requires_tool=1".into());
}
if d.no_collision {
parts.push("no_collision=1".into());
}
if d.slipperiness > 0.0 {
parts.push(format!("slipperiness={}", d.slipperiness));
}
if d.connects {
parts.push("connects=1".into());
}
if !d.connect_groups.is_empty() {
parts.push(format!("connect_groups={}", d.connect_groups.join(",")));
}
if let Some(inv) = &d.inventory_id {
parts.push(format!("inventory_id={inv}"));
}
parts.join("\t")
})
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeInventoryDefs<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.inventories
.iter()
.map(|d| {
let mut parts = vec![d.id.clone()];
parts.push(format!("slot_count={}", d.slot_count));
parts.push(format!(
"layout={}",
yog_inventory::encode_layout(&d.resolved_layout())
));
parts.push(format!(
"include_player_inventory={}",
d.include_player_inventory as u8
));
parts.push(format!(
"player_inv={}:{}",
d.player_inv_offset.0, d.player_inv_offset.1
));
if let Some(tex) = &d.background_texture {
parts.push(format!("background_texture={tex}"));
}
if !d.title.is_empty() {
parts.push(format!("title={}", d.title));
}
if let Some((w, h)) = d.background_size {
parts.push(format!("background_size={w}:{h}"));
}
parts.join("\t")
})
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPacket<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
channel: JString<'l>,
player: JString<'l>,
payload: JByteArray<'l>,
) {
let ch = env
.get_string(&channel)
.map(String::from)
.unwrap_or_default();
let pl = env
.get_string(&player)
.map(String::from)
.unwrap_or_default();
let data = env.convert_byte_array(&payload).unwrap_or_default();
let ev = yog_abi::YogPacketEvent {
channel: YogStr::from_str(&ch),
player: YogStr::from_str(&pl),
payload: data.as_ptr(),
payload_len: data.len() as u32,
};
let h = handlers();
let srv = srv_ptr();
guard("on_packet", || {
if let Some((module, ud, f)) = h.packets.get(&ch) {
if module.is_retiring() {
return;
}
let Some(_call) = module.enter() else { return };
unsafe { f(*ud, srv, &ev) };
}
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientPacket<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
channel: JString<'l>,
payload: JByteArray<'l>,
) {
let ch = env
.get_string(&channel)
.map(String::from)
.unwrap_or_default();
let data = env.convert_byte_array(&payload).unwrap_or_default();
let ev = yog_abi::YogPacketEvent {
channel: YogStr::from_str(&ch),
player: YogStr::EMPTY,
payload: data.as_ptr(),
payload_len: data.len() as u32,
};
let h = handlers();
let srv = srv_ptr();
guard("on_client_packet", || {
if let Some((module, ud, f)) = h.client_packets.get(&ch) {
if module.is_retiring() {
return;
}
let Some(_call) = module.enter() else { return };
unsafe { f(*ud, srv, &ev) };
}
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativePacketChannels<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.packets
.keys()
.cloned()
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeClientPacketChannels<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let s = handlers()
.client_packets
.keys()
.cloned()
.collect::<Vec<_>>()
.join("\n");
env.new_string(s)
.map(jni::objects::JString::into_raw)
.unwrap_or(std::ptr::null_mut())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawn<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
entity_type: JString<'l>,
uuid: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.entity_spawn.is_empty() {
return;
}
let (et, u, d) = (
jstr!(env, entity_type),
jstr!(env, uuid),
jstr!(env, dimension),
);
let ev = yog_abi::YogEntitySpawnEvent {
entity_type: YogStr::from_str(&et),
uuid: YogStr::from_str(&u),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_entity_spawn", || {
each(&h.entity_spawn, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawnPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
entity_type: JString<'l>,
uuid: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.entity_spawn.is_empty() {
return 1;
}
let et = jstr!(env, entity_type, 1);
let u = jstr!(env, uuid, 1);
let d = jstr!(env, dimension, 1);
let ev = yog_abi::YogEntitySpawnEvent {
entity_type: YogStr::from_str(&et),
uuid: YogStr::from_str(&u),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.entity_spawn, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamagePre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
entity_type: JString<'l>,
uuid: JString<'l>,
amount: jfloat,
source: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.entity_damage.is_empty() {
return 1;
}
let et = jstr!(env, entity_type, 1);
let u = jstr!(env, uuid, 1);
let s = jstr!(env, source, 1);
let d = jstr!(env, dimension, 1);
let ev = yog_abi::YogEntityDamageEvent {
entity_type: YogStr::from_str(&et),
uuid: YogStr::from_str(&u),
amount,
source: YogStr::from_str(&s),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.entity_damage, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlockPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
block: JString<'l>,
x: jint,
y: jint,
z: jint,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.player_place_block.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let b = jstr!(env, block, 1);
let d = jstr!(env, dimension, 1);
let ev = YogPlaceBlockEvent {
player: YogStr::from_str(&p),
block: YogStr::from_str(&b),
pos: YogBlockPos { x, y, z },
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.player_place_block, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlock<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
block: JString<'l>,
x: jint,
y: jint,
z: jint,
dimension: JString<'l>,
) {
let h = handlers();
if h.player_place_block.is_empty() {
return;
}
let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
let ev = YogPlaceBlockEvent {
player: YogStr::from_str(&p),
block: YogStr::from_str(&b),
pos: YogBlockPos { x, y, z },
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_player_place_block", || {
each(&h.player_place_block, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeathPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
uuid: JString<'l>,
source: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.player_death.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let u = jstr!(env, uuid, 1);
let s = jstr!(env, source, 1);
let d = jstr!(env, dimension, 1);
let ev = YogPlayerDeathEvent {
player: YogStr::from_str(&p),
uuid: YogStr::from_str(&u),
source: YogStr::from_str(&s),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.player_death, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeath<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
uuid: JString<'l>,
source: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.player_death.is_empty() {
return;
}
let (p, u, s, d) = (
jstr!(env, player),
jstr!(env, uuid),
jstr!(env, source),
jstr!(env, dimension),
);
let ev = YogPlayerDeathEvent {
player: YogStr::from_str(&p),
uuid: YogStr::from_str(&u),
source: YogStr::from_str(&s),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_player_death", || {
each(&h.player_death, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerRespawn<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
uuid: JString<'l>,
at_anchor: jni::sys::jboolean,
dimension: JString<'l>,
) {
let h = handlers();
if h.player_respawn.is_empty() {
return;
}
let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
let ev = YogPlayerRespawnEvent {
player: YogStr::from_str(&p),
uuid: YogStr::from_str(&u),
at_anchor: at_anchor != 0,
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_player_respawn", || {
each(&h.player_respawn, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAdvancement<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
uuid: JString<'l>,
advancement: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.advancement.is_empty() {
return;
}
let (p, u, a, d) = (
jstr!(env, player),
jstr!(env, uuid),
jstr!(env, advancement),
jstr!(env, dimension),
);
let ev = YogAdvancementEvent {
player: YogStr::from_str(&p),
uuid: YogStr::from_str(&u),
advancement: YogStr::from_str(&a),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_advancement", || {
each(&h.advancement, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteractPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
entity_type: JString<'l>,
entity_uuid: JString<'l>,
hand: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.entity_interact.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let pu = jstr!(env, player_uuid, 1);
let et = jstr!(env, entity_type, 1);
let eu = jstr!(env, entity_uuid, 1);
let ha = jstr!(env, hand, 1);
let d = jstr!(env, dimension, 1);
let ev = YogEntityInteractEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
entity_type: YogStr::from_str(&et),
entity_uuid: YogStr::from_str(&eu),
hand: YogStr::from_str(&ha),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.entity_interact, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteract<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
entity_type: JString<'l>,
entity_uuid: JString<'l>,
hand: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.entity_interact.is_empty() {
return;
}
let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
let (et, eu, ha, d) = (
jstr!(env, entity_type),
jstr!(env, entity_uuid),
jstr!(env, hand),
jstr!(env, dimension),
);
let ev = YogEntityInteractEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
entity_type: YogStr::from_str(&et),
entity_uuid: YogStr::from_str(&eu),
hand: YogStr::from_str(&ha),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_entity_interact", || {
each(&h.entity_interact, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemCraft<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
result_item: JString<'l>,
result_count: jint,
dimension: JString<'l>,
) {
let h = handlers();
if h.item_craft.is_empty() {
return;
}
let (p, pu, ri, d) = (
jstr!(env, player),
jstr!(env, player_uuid),
jstr!(env, result_item),
jstr!(env, dimension),
);
let ev = YogCraftEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
result_item: YogStr::from_str(&ri),
result_count: result_count as u32,
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_item_craft", || {
each(&h.item_craft, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosionPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
dimension: JString<'l>,
x: jdouble,
y: jdouble,
z: jdouble,
power: jfloat,
cause_uuid: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.explosion.is_empty() {
return 1;
}
let d = jstr!(env, dimension, 1);
let cu = jstr!(env, cause_uuid, 1);
let ev = YogExplosionEvent {
dimension: YogStr::from_str(&d),
x,
y,
z,
power,
cause_uuid: YogStr::from_str(&cu),
};
let srv = srv_ptr();
allowed(&h.explosion, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosion<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
dimension: JString<'l>,
x: jdouble,
y: jdouble,
z: jdouble,
power: jfloat,
cause_uuid: JString<'l>,
) {
let h = handlers();
if h.explosion.is_empty() {
return;
}
let (d, cu) = (jstr!(env, dimension), jstr!(env, cause_uuid));
let ev = YogExplosionEvent {
dimension: YogStr::from_str(&d),
x,
y,
z,
power,
cause_uuid: YogStr::from_str(&cu),
};
let srv = srv_ptr();
guard("on_explosion", || {
each(&h.explosion, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickupPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
item_id: JString<'l>,
item_count: jint,
entity_uuid: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.item_pickup.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let pu = jstr!(env, player_uuid, 1);
let ii = jstr!(env, item_id, 1);
let eu = jstr!(env, entity_uuid, 1);
let d = jstr!(env, dimension, 1);
let ev = YogItemPickupEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
item_id: YogStr::from_str(&ii),
item_count: item_count as u32,
entity_uuid: YogStr::from_str(&eu),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.item_pickup, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickup<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
item_id: JString<'l>,
item_count: jint,
entity_uuid: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.item_pickup.is_empty() {
return;
}
let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
let (ii, eu, d) = (
jstr!(env, item_id),
jstr!(env, entity_uuid),
jstr!(env, dimension),
);
let ev = YogItemPickupEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
item_id: YogStr::from_str(&ii),
item_count: item_count as u32,
entity_uuid: YogStr::from_str(&eu),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_item_pickup", || {
each(&h.item_pickup, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerMove<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
x: jdouble,
y: jdouble,
z: jdouble,
yaw: jfloat,
pitch: jfloat,
dimension: JString<'l>,
) {
let h = handlers();
if h.player_move.is_empty() {
return;
}
let (p, pu, d) = (
jstr!(env, player),
jstr!(env, player_uuid),
jstr!(env, dimension),
);
let ev = YogPlayerMoveEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
x,
y,
z,
yaw,
pitch,
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_player_move", || {
each(&h.player_move, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpenPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.container_open.is_empty() {
return 1;
}
let p = jstr!(env, player, 1);
let pu = jstr!(env, player_uuid, 1);
let d = jstr!(env, dimension, 1);
let ev = YogContainerOpenEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
container_type: YogStr::EMPTY,
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
allowed(&h.container_open, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpen<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
container_type: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.container_open.is_empty() {
return;
}
let (p, pu, ct, d) = (
jstr!(env, player),
jstr!(env, player_uuid),
jstr!(env, container_type),
jstr!(env, dimension),
);
let ev = YogContainerOpenEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
container_type: YogStr::from_str(&ct),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_container_open", || {
each(&h.container_open, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerClose<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
player: JString<'l>,
player_uuid: JString<'l>,
dimension: JString<'l>,
) {
let h = handlers();
if h.container_close.is_empty() {
return;
}
let (p, pu, d) = (
jstr!(env, player),
jstr!(env, player_uuid),
jstr!(env, dimension),
);
let ev = YogContainerCloseEvent {
player: YogStr::from_str(&p),
player_uuid: YogStr::from_str(&pu),
dimension: YogStr::from_str(&d),
};
let srv = srv_ptr();
guard("on_container_close", || {
each(&h.container_close, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHitPre<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
projectile_type: JString<'l>,
projectile_uuid: JString<'l>,
shooter_uuid: JString<'l>,
hit_type: JString<'l>,
hit_entity_uuid: JString<'l>,
x: jdouble,
y: jdouble,
z: jdouble,
dimension: JString<'l>,
) -> jni::sys::jboolean {
let h = handlers();
if h.projectile_hit.is_empty() {
return 1;
}
let pt = jstr!(env, projectile_type, 1);
let pu = jstr!(env, projectile_uuid, 1);
let su = jstr!(env, shooter_uuid, 1);
let ht = jstr!(env, hit_type, 1);
let heu = jstr!(env, hit_entity_uuid, 1);
let dim = jstr!(env, dimension, 1);
let ev = YogProjectileHitEvent {
projectile_type: YogStr::from_str(&pt),
projectile_uuid: YogStr::from_str(&pu),
shooter_uuid: YogStr::from_str(&su),
hit_type: YogStr::from_str(&ht),
hit_entity_uuid: YogStr::from_str(&heu),
x,
y,
z,
dimension: YogStr::from_str(&dim),
};
let srv = srv_ptr();
allowed(&h.projectile_hit, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHit<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
projectile_type: JString<'l>,
projectile_uuid: JString<'l>,
shooter_uuid: JString<'l>,
hit_type: JString<'l>,
hit_entity_uuid: JString<'l>,
x: jdouble,
y: jdouble,
z: jdouble,
dimension: JString<'l>,
) {
let h = handlers();
if h.projectile_hit.is_empty() {
return;
}
let (pt, pu) = (jstr!(env, projectile_type), jstr!(env, projectile_uuid));
let (su, ht) = (jstr!(env, shooter_uuid), jstr!(env, hit_type));
let (heu, dim) = (jstr!(env, hit_entity_uuid), jstr!(env, dimension));
let ev = YogProjectileHitEvent {
projectile_type: YogStr::from_str(&pt),
projectile_uuid: YogStr::from_str(&pu),
shooter_uuid: YogStr::from_str(&su),
hit_type: YogStr::from_str(&ht),
hit_entity_uuid: YogStr::from_str(&heu),
x,
y,
z,
dimension: YogStr::from_str(&dim),
};
let srv = srv_ptr();
guard("on_projectile_hit", || {
each(&h.projectile_hit, |ud, f| unsafe { f(ud, srv, &ev, 1) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientTick<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
) {
let h = handlers();
if h.client_tick.is_empty() {
return;
}
guard("on_client_tick", || {
each(&h.client_tick, |ud, f| unsafe { f(ud) });
});
}
fn gl_proc_address(sym: &str) -> usize {
let ptr: Option<i64> = unsafe { bridge!("glProcAddress", [S(YogStr::from_str(sym))]) };
ptr.unwrap_or(0) as usize
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeGlInit<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
) {
if GL.get().is_some() {
return;
}
let mut raw_get_binary: usize = 0;
let mut raw_prog_binary: usize = 0;
let gl = unsafe {
glow::Context::from_loader_function(|sym| {
let ptr = gl_proc_address(sym);
match sym {
"glGetProgramBinary" => raw_get_binary = ptr,
"glProgramBinary" => raw_prog_binary = ptr,
_ => {}
}
ptr as *const _
})
};
let _ = GL.set(GlCtx(gl));
let _ = GL_GET_PROGRAM_BINARY.set((raw_get_binary != 0).then_some(raw_get_binary));
let _ = GL_PROGRAM_BINARY.set((raw_prog_binary != 0).then_some(raw_prog_binary));
let get_program_iv = gl_proc_address("glGetProgramiv");
let _ = GL_GET_PROGRAM_IV.set((get_program_iv != 0).then_some(get_program_iv));
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnHudRender<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
delta_tick: jfloat,
screen_w: jint,
screen_h: jint,
scale_factor: jfloat,
player_x: jfloat,
player_y: jfloat,
player_z: jfloat,
) {
let h = handlers();
let mut gfx = GFX_FN_TABLE;
gfx.screen_w = screen_w;
gfx.screen_h = screen_h;
gfx.delta_tick = delta_tick;
gfx.scale_factor = scale_factor;
gfx.player_pos = [player_x, player_y, player_z];
if !h.hud_render.is_empty() {
guard("on_hud_render", || {
each(&h.hud_render, |ud, f| unsafe { f(ud, &gfx) });
});
}
let active_layers: Vec<UiLayer> = h.active_uis.lock().unwrap().clone();
let sw = screen_w as f32;
let sh = screen_h as f32;
let ctx = unsafe { yog_gfx::GfxContext::from_raw(&gfx as *const _) };
let mut renderers = h.book_renderers.lock().unwrap();
for layer in &active_layers {
if !layer.visible {
continue;
}
if let Some(renderer) = renderers.get_mut(&layer.id) {
renderer.render(&ctx, sw, sh);
}
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnWorldRender<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
delta_tick: jfloat,
screen_w: jint,
screen_h: jint,
scale_factor: jfloat,
view_proj_arr: JFloatArray<'l>,
cam_x: jfloat,
cam_y: jfloat,
cam_z: jfloat,
player_x: jfloat,
player_y: jfloat,
player_z: jfloat,
) {
let h = handlers();
if h.world_render.is_empty() {
return;
}
let mut view_proj = [0f32; 16];
if env
.get_float_array_region(&view_proj_arr, 0, &mut view_proj)
.is_err()
{
return;
}
let mut gfx = GFX_FN_TABLE;
gfx.screen_w = screen_w;
gfx.screen_h = screen_h;
gfx.delta_tick = delta_tick;
gfx.scale_factor = scale_factor;
gfx.view_proj = view_proj;
gfx.camera_pos = [cam_x, cam_y, cam_z];
gfx.player_pos = [player_x, player_y, player_z];
guard("on_world_render", || {
each(&h.world_render, |ud, f| unsafe { f(ud, &gfx) });
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnKeyPress<'l>(
_env: JNIEnv<'l>,
_class: JClass<'l>,
key_code: jint,
scan_code: jint,
action: jint,
modifiers: jint,
) -> jni::sys::jboolean {
let h = handlers();
if h.key_press.is_empty() {
return 1;
}
let ev = YogKeyPressEvent {
key_code,
scan_code,
action,
modifiers,
};
allowed(&h.key_press, |ud, f| unsafe { f(ud, &ev) }) as jni::sys::jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenOpen<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
screen_class: JString<'l>,
) {
let h = handlers();
if h.screen_open.is_empty() {
return;
}
let sc = match env.get_string(&screen_class) {
Ok(s) => String::from(s),
Err(_) => return,
};
guard("on_screen_open", || {
each(&h.screen_open, |ud, f| unsafe {
f(ud, YogStr::from_str(&sc))
});
});
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenClose<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
screen_class: JString<'l>,
) {
let h = handlers();
if h.screen_close.is_empty() {
return;
}
let sc = match env.get_string(&screen_class) {
Ok(s) => String::from(s),
Err(_) => return,
};
guard("on_screen_close", || {
each(&h.screen_close, |ud, f| unsafe {
f(ud, YogStr::from_str(&sc))
});
});
}
mod client_api;
mod control_socket;
mod images;
mod jcall;
mod ui_jni;