1use std::collections::HashMap;
16use std::num::NonZeroU32;
17use std::os::raw::c_void;
18use std::path::{Path, PathBuf};
19use std::sync::{Arc, Mutex, OnceLock, RwLock};
20
21use glow::HasContext;
22use jcall::JArg;
23use jni::objects::{JByteArray, JClass, JFloatArray, JString};
24use jni::sys::{jdouble, jfloat, jint, jstring};
25use jni::{JNIEnv, JavaVM};
26use libloading::Symbol;
27use yog_hot_reload::{HotReloader, LoadedModule, ModuleGeneration, ModuleRegistry};
28
29use yog_abi::{
30 ABI_VERSION, YogAdvancementEvent, YogAdvancementFn, YogApi, YogAttackEntityFn, YogBlockBreakFn,
31 YogBlockPos, YogChatFn, YogClientFn, YogCommandFn, YogContainerCloseEvent,
32 YogContainerCloseFn, YogContainerOpenEvent, YogContainerOpenFn, YogCraftEvent, YogCraftFn,
33 YogEntityDamageFn, YogEntityDeathFn, YogEntityInteractEvent, YogEntityInteractFn,
34 YogEntitySpawnFn, YogExplosionEvent, YogExplosionFn, YogGfxApi, YogHudRenderFn, YogItemInsertEvent, YogItemInsertFn, YogItemPickupEvent, YogItemPickupFn, YogKeyPressEvent, YogMachineCraftEvent, YogMachineCraftFn, YogKeyPressFn, YogOwnedStr, YogPacketFn,
35 YogPlaceBlockEvent, YogPlaceBlockFn, YogPlayerDeathEvent, YogPlayerDeathFn, YogPlayerFn,
36 YogPlayerMoveEvent, YogPlayerMoveFn, YogPlayerRespawnEvent, YogPlayerRespawnFn,
37 YogProjectileHitEvent, YogProjectileHitFn, YogScheduledFn, YogScreenFn, YogServer, YogServerFn,
38 YogStr, YogUseBlockFn, YogUseItemFn, YogVec3, YogWorldRenderFn,
39};
40use yog_registry::{BlockDef, ItemDef};
41
42static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();
46static HOT_RELOADER: OnceLock<HotReloader<RuntimeModuleRegistry>> = OnceLock::new();
51static CURRENT_REGISTRATION: Mutex<Option<LoadedModule>> = Mutex::new(None);
59static MOD_INFOS: Mutex<Vec<[String; 7]>> = Mutex::new(Vec::new());
63static INTEROP_SYMBOLS: OnceLock<Mutex<HashMap<String, HashMap<String, usize>>>> = OnceLock::new();
66static SERVER: OnceLock<YogServer> = OnceLock::new();
68static API_TABLE: OnceLock<YogApi> = OnceLock::new();
76static HANDLERS: OnceLock<RwLock<RuntimeHandlers>> = OnceLock::new();
84
85struct GlCtx(glow::Context);
88unsafe impl Send for GlCtx {}
89unsafe impl Sync for GlCtx {}
90
91static GL: OnceLock<GlCtx> = OnceLock::new();
93
94static GL_GET_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
98static GL_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
99static GL_GET_PROGRAM_IV: OnceLock<Option<usize>> = OnceLock::new();
100
101#[derive(Debug, Clone)]
104pub struct UiLayer {
105 pub id: String,
106 pub parent: Option<String>,
107 pub modal: bool,
108 pub pause_game: bool,
109 pub visible: bool,
110 pub enabled: bool,
111 pub z_index: i32,
112}
113
114type TooltipEntry = (LoadedModule, *mut c_void, yog_abi::YogTooltipSizeFn, yog_abi::YogTooltipDrawFn);
118type ItemTextEntry = (LoadedModule, *mut c_void, yog_abi::YogItemTextFn);
119type CommandEntry = (LoadedModule, Option<String>, *mut c_void, YogCommandFn);
120
121#[derive(Default)]
132struct RuntimeHandlers {
133 block_break: Vec<(LoadedModule, *mut c_void, YogBlockBreakFn)>,
134 chat: Vec<(LoadedModule, *mut c_void, YogChatFn)>,
135 player_join: Vec<(LoadedModule, *mut c_void, YogPlayerFn)>,
136 player_leave: Vec<(LoadedModule, *mut c_void, YogPlayerFn)>,
137 use_item: Vec<(LoadedModule, *mut c_void, YogUseItemFn)>,
138 use_block: Vec<(LoadedModule, *mut c_void, YogUseBlockFn)>,
139 attack_entity: Vec<(LoadedModule, *mut c_void, YogAttackEntityFn)>,
140 entity_damage: Vec<(LoadedModule, *mut c_void, YogEntityDamageFn)>,
141 entity_death: Vec<(LoadedModule, *mut c_void, YogEntityDeathFn)>,
142 entity_spawn: Vec<(LoadedModule, *mut c_void, YogEntitySpawnFn)>,
143 player_place_block: Vec<(LoadedModule, *mut c_void, YogPlaceBlockFn)>,
144 player_death: Vec<(LoadedModule, *mut c_void, YogPlayerDeathFn)>,
145 player_respawn: Vec<(LoadedModule, *mut c_void, YogPlayerRespawnFn)>,
146 advancement: Vec<(LoadedModule, *mut c_void, YogAdvancementFn)>,
147 entity_interact: Vec<(LoadedModule, *mut c_void, YogEntityInteractFn)>,
148 item_craft: Vec<(LoadedModule, *mut c_void, YogCraftFn)>,
149 machine_craft: Vec<(LoadedModule, *mut c_void, YogMachineCraftFn)>,
150 explosion: Vec<(LoadedModule, *mut c_void, YogExplosionFn)>,
151 item_pickup: Vec<(LoadedModule, *mut c_void, YogItemPickupFn)>,
152 item_insert: Vec<(LoadedModule, *mut c_void, YogItemInsertFn)>,
153 player_move: Vec<(LoadedModule, *mut c_void, YogPlayerMoveFn)>,
154 container_open: Vec<(LoadedModule, *mut c_void, YogContainerOpenFn)>,
155 container_close: Vec<(LoadedModule, *mut c_void, YogContainerCloseFn)>,
156 projectile_hit: Vec<(LoadedModule, *mut c_void, YogProjectileHitFn)>,
157 client_tick: Vec<(LoadedModule, *mut c_void, YogClientFn)>,
158 hud_render: Vec<(LoadedModule, *mut c_void, YogHudRenderFn)>,
159 body: Vec<(LoadedModule, *mut c_void, yog_abi::YogBodyFn)>,
160 camera: Vec<(LoadedModule, *mut c_void, yog_abi::YogCameraFn)>,
161 travel: Vec<(LoadedModule, *mut c_void, yog_abi::YogTravelFn)>,
162 world_render: Vec<(LoadedModule, *mut c_void, YogWorldRenderFn)>,
163 key_press: Vec<(LoadedModule, *mut c_void, YogKeyPressFn)>,
164 screen_open: Vec<(LoadedModule, *mut c_void, YogScreenFn)>,
165 screen_close: Vec<(LoadedModule, *mut c_void, YogScreenFn)>,
166 server_tick: Vec<(LoadedModule, *mut c_void, YogServerFn)>,
167 server_started: Vec<(LoadedModule, *mut c_void, YogServerFn)>,
168 server_stopping: Vec<(LoadedModule, *mut c_void, YogServerFn)>,
169 commands: HashMap<String, Vec<CommandEntry>>,
179 recipes: Vec<(String, String, yog_data::Value)>,
181 packets: HashMap<String, (LoadedModule, *mut c_void, YogPacketFn)>,
182 client_packets: HashMap<String, (LoadedModule, *mut c_void, YogPacketFn)>,
183 items: Vec<ItemDef>,
184 blocks: Vec<BlockDef>,
185 content_blob: Mutex<Option<Arc<Vec<u8>>>>,
188 sound_groups: HashMap<String, yog_registry::SoundGroup>,
190 tool_materials: HashMap<String, yog_registry::ToolMaterial>,
192 biome_features: Vec<yog_registry::BiomeFeature>,
194 armor_materials: HashMap<String, yog_registry::ArmorMaterial>,
195 inventories: Vec<yog_inventory::InventoryDef>,
196 books: std::collections::HashSet<String>,
198 book_renderers: Mutex<HashMap<String, yog_book::BookRenderer>>,
199 dimensions: HashMap<String, yog_data::Value>,
203 chunk_generators: HashMap<String, (LoadedModule, *mut c_void, yog_abi::YogChunkGeneratorFn)>,
204 terrains: HashMap<String, Arc<yog_worldgen::Generator>>,
207 ui_handlers: HashMap<String, (LoadedModule, *mut c_void, yog_abi::YogUIEventFn)>,
208 ui_render_handlers: HashMap<String, Vec<(LoadedModule, *mut c_void, YogHudRenderFn)>>,
209 page_drawers: HashMap<String, (LoadedModule, *mut c_void, yog_abi::YogPageDrawFn)>,
211 item_tooltips: HashMap<String, Vec<TooltipEntry>>,
213 item_texts: HashMap<String, Vec<ItemTextEntry>>,
215 menu_entries: Vec<(String, String)>, pub active_uis: Mutex<Vec<UiLayer>>,
217 startup_grants: Vec<yog_registry::StartupGrant>,
218 pending_grants: Mutex<Vec<(String, String, u32)>>,
222 scheduler: Mutex<SchedulerState>,
223}
224
225unsafe impl Send for RuntimeHandlers {}
227unsafe impl Sync for RuntimeHandlers {}
228
229#[derive(Default)]
230struct SchedulerState {
231 once_tasks: Vec<OnceTask>,
232 repeating_tasks: Vec<RepeatingTask>,
233}
234
235impl SchedulerState {
236 fn due(&mut self) -> Vec<(LoadedModule, *mut c_void, YogScheduledFn)> {
241 let mut out = Vec::new();
242 self.once_tasks.retain_mut(|t| {
243 if t.delay_remaining <= 1 {
244 out.push((t.module.clone(), t.ud, t.f));
245 false
246 } else {
247 t.delay_remaining -= 1;
248 true
249 }
250 });
251 for t in &mut self.repeating_tasks {
252 t.ticks_left = t.ticks_left.saturating_sub(1);
253 if t.ticks_left == 0 {
254 out.push((t.module.clone(), t.ud, t.f));
255 t.ticks_left = t.period.max(1);
256 }
257 }
258 out
259 }
260}
261
262struct OnceTask {
263 module: LoadedModule,
264 delay_remaining: u64,
265 ud: *mut c_void,
266 f: YogScheduledFn,
267}
268struct RepeatingTask {
269 module: LoadedModule,
270 period: u64,
271 ticks_left: u64,
272 ud: *mut c_void,
273 f: YogScheduledFn,
274}
275
276unsafe impl Send for SchedulerState {}
277unsafe impl Sync for SchedulerState {}
278unsafe impl Send for OnceTask {}
279unsafe impl Send for RepeatingTask {}
280
281type Entry<F> = (LoadedModule, *mut c_void, F);
283
284fn each_while<F: Copy>(entries: &[Entry<F>], mut call: impl FnMut(*mut c_void, F) -> bool) -> bool {
287 for (module, ud, f) in entries {
288 if module.is_retiring() {
289 continue;
290 }
291 let Some(_call) = module.enter() else {
292 continue;
293 };
294 if !call(*ud, *f) {
295 return false;
296 }
297 }
298 true
299}
300
301fn each<F: Copy, R>(entries: &[Entry<F>], mut call: impl FnMut(*mut c_void, F) -> R) {
304 each_while(entries, |ud, f| {
305 call(ud, f);
306 true
307 });
308}
309
310fn allowed<F: Copy>(entries: &[Entry<F>], call: impl FnMut(*mut c_void, F) -> bool) -> bool {
313 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| each_while(entries, call)))
314 .unwrap_or(true)
315}
316
317fn handlers() -> std::sync::RwLockReadGuard<'static, RuntimeHandlers> {
318 HANDLERS
319 .get()
320 .expect("yog: nativeInit not called yet")
321 .read()
322 .expect("handlers lock poisoned")
323}
324
325fn current_registration() -> LoadedModule {
326 CURRENT_REGISTRATION
327 .lock()
328 .unwrap()
329 .clone()
330 .expect("a yog-runtime api_* function was called outside of yog_mod_register")
331}
332
333fn hot_reloader() -> &'static HotReloader<RuntimeModuleRegistry> {
334 HOT_RELOADER.get().expect("yog: nativeInit not called yet")
335}
336
337macro_rules! for_each_handler_list {
346 ($h:expr_2021, |$list:ident| $body:expr_2021) => {{
347 {
348 let $list = &mut $h.block_break;
349 $body;
350 }
351 {
352 let $list = &mut $h.chat;
353 $body;
354 }
355 {
356 let $list = &mut $h.player_join;
357 $body;
358 }
359 {
360 let $list = &mut $h.player_leave;
361 $body;
362 }
363 {
364 let $list = &mut $h.use_item;
365 $body;
366 }
367 {
368 let $list = &mut $h.use_block;
369 $body;
370 }
371 {
372 let $list = &mut $h.attack_entity;
373 $body;
374 }
375 {
376 let $list = &mut $h.entity_damage;
377 $body;
378 }
379 {
380 let $list = &mut $h.entity_death;
381 $body;
382 }
383 {
384 let $list = &mut $h.entity_spawn;
385 $body;
386 }
387 {
388 let $list = &mut $h.player_place_block;
389 $body;
390 }
391 {
392 let $list = &mut $h.player_death;
393 $body;
394 }
395 {
396 let $list = &mut $h.player_respawn;
397 $body;
398 }
399 {
400 let $list = &mut $h.advancement;
401 $body;
402 }
403 {
404 let $list = &mut $h.entity_interact;
405 $body;
406 }
407 {
408 let $list = &mut $h.item_craft;
409 $body;
410 }
411 {
412 let $list = &mut $h.machine_craft;
413 $body;
414 }
415 {
416 let $list = &mut $h.explosion;
417 $body;
418 }
419 {
420 let $list = &mut $h.item_pickup;
421 $body;
422 }
423 {
424 let $list = &mut $h.item_insert;
425 $body;
426 }
427 {
428 let $list = &mut $h.player_move;
429 $body;
430 }
431 {
432 let $list = &mut $h.container_open;
433 $body;
434 }
435 {
436 let $list = &mut $h.container_close;
437 $body;
438 }
439 {
440 let $list = &mut $h.projectile_hit;
441 $body;
442 }
443 {
444 let $list = &mut $h.client_tick;
445 $body;
446 }
447 {
448 let $list = &mut $h.hud_render;
449 $body;
450 }
451 {
452 let $list = &mut $h.body;
453 $body;
454 }
455 {
456 let $list = &mut $h.camera;
457 $body;
458 }
459 {
460 let $list = &mut $h.travel;
461 $body;
462 }
463 {
464 let $list = &mut $h.world_render;
465 $body;
466 }
467 {
468 let $list = &mut $h.key_press;
469 $body;
470 }
471 {
472 let $list = &mut $h.screen_open;
473 $body;
474 }
475 {
476 let $list = &mut $h.screen_close;
477 $body;
478 }
479 {
480 let $list = &mut $h.server_tick;
481 $body;
482 }
483 {
484 let $list = &mut $h.server_started;
485 $body;
486 }
487 {
488 let $list = &mut $h.server_stopping;
489 $body;
490 }
491 }};
492}
493
494struct RuntimeModuleRegistry;
495
496impl ModuleRegistry for RuntimeModuleRegistry {
497 fn retarget(&self, _mod_id: &str, _from: ModuleGeneration, _to: ModuleGeneration) {}
498
499 fn purge(&self, generation: ModuleGeneration) {
500 let Some(lock) = HANDLERS.get() else { return };
501 let mut h = lock.write().expect("handlers lock poisoned");
502 let h = &mut *h;
503 for_each_handler_list!(h, |list| list
504 .retain(|(m, ..)| m.generation() != generation));
505 for entries in h.commands.values_mut() {
506 entries.retain(|(m, ..)| m.generation() != generation);
507 }
508 h.packets.retain(|_, (m, ..)| m.generation() != generation);
509 h.client_packets
510 .retain(|_, (m, ..)| m.generation() != generation);
511 h.chunk_generators
512 .retain(|_, (m, ..)| m.generation() != generation);
513 h.ui_handlers
514 .retain(|_, (m, ..)| m.generation() != generation);
515 for entries in h.ui_render_handlers.values_mut() {
516 entries.retain(|(m, ..)| m.generation() != generation);
517 }
518 h.page_drawers
519 .retain(|_, (m, ..)| m.generation() != generation);
520 for entries in h.item_texts.values_mut() {
521 entries.retain(|(m, ..)| m.generation() != generation);
522 }
523 for entries in h.item_tooltips.values_mut() {
524 entries.retain(|(m, ..)| m.generation() != generation);
525 }
526 h.scheduler
527 .lock()
528 .expect("scheduler poisoned")
529 .once_tasks
530 .retain(|t| t.module.generation() != generation);
531 h.scheduler
532 .lock()
533 .expect("scheduler poisoned")
534 .repeating_tasks
535 .retain(|t| t.module.generation() != generation);
536 }
537}
538
539fn guard(label: &str, f: impl FnOnce()) {
542 if std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).is_err() {
543 yog_logging::error!("a mod panicked handling `{}` (ignored)", label);
544 }
545}
546
547macro_rules! jstr {
550 ($env:expr_2021, $s:expr_2021) => {
551 jstr!($env, $s, Default::default())
552 };
553 ($env:expr_2021, $s:expr_2021, $or:expr_2021) => {
554 match $env.get_string(&$s) {
555 Ok(s) => String::from(s),
556 Err(_) => return $or,
557 }
558 };
559}
560
561fn get_env() -> Option<jni::AttachGuard<'static>> {
562 JAVA_VM.get()?.attach_current_thread().ok()
563}
564
565macro_rules! host {
568 ($class:literal, $name:literal, [$($arg:expr_2021),* $(,)?]) => {
569 match $crate::get_env() {
570 Some(mut env) => $crate::jcall::jcall!(&mut env, $class, $name, [$($arg),*]),
571 None => None,
572 }
573 };
574}
575
576macro_rules! bridge {
578 ($name:literal, [$($arg:expr_2021),* $(,)?]) => {
579 host!("dev/yog/NativeBridge", $name, [$($arg),*])
580 };
581}
582
583fn owned(s: Option<String>) -> YogOwnedStr {
585 s.map_or(YogOwnedStr::NONE, YogOwnedStr::from_string)
586}
587
588fn parse_tsv<T: std::str::FromStr, const N: usize>(s: &str) -> Option<[T; N]> {
590 let values: Vec<T> = s
591 .split('\t')
592 .take(N)
593 .map(|v| v.parse().ok())
594 .collect::<Option<_>>()?;
595 values.try_into().ok()
596}
597
598unsafe extern "C" fn yog_free_str(ptr: *mut u8, len: u32) {
601 unsafe {
602 if !ptr.is_null() {
603 drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
604 ptr,
605 len as usize,
606 )));
607 }
608 }
609}
610
611unsafe extern "C" fn srv_broadcast(_ctx: *mut c_void, msg: YogStr) {
616 unsafe {
617 let _: Option<()> = bridge!("broadcast", [S(msg)]);
618 }
619}
620
621unsafe extern "C" fn srv_get_block(
622 _ctx: *mut c_void,
623 dim: YogStr,
624 pos: YogBlockPos,
625) -> YogOwnedStr {
626 unsafe { owned(bridge!("getBlock", [S(dim), I(pos.x), I(pos.y), I(pos.z)])) }
627}
628
629unsafe extern "C" fn srv_set_block(
630 _ctx: *mut c_void,
631 dim: YogStr,
632 pos: YogBlockPos,
633 block: YogStr,
634) -> bool {
635 unsafe {
636 bridge!("setBlock", [S(dim), I(pos.x), I(pos.y), I(pos.z), S(block)]).unwrap_or(false)
637 }
638}
639
640unsafe extern "C" fn srv_world_time(_ctx: *mut c_void, dim: YogStr, out: *mut i64) -> bool {
641 unsafe {
642 match bridge!("worldTime", [S(dim)]) {
643 Some(v) if v != i64::MIN => {
644 *out = v;
645 true
646 }
647 _ => false,
648 }
649 }
650}
651
652unsafe extern "C" fn srv_set_time(_ctx: *mut c_void, dim: YogStr, time: i64) -> bool {
653 unsafe { bridge!("worldSetTime", [S(dim), J(time)]).unwrap_or(false) }
654}
655
656unsafe extern "C" fn srv_is_raining(_ctx: *mut c_void, dim: YogStr) -> bool {
657 unsafe { bridge!("worldIsRaining", [S(dim)]).unwrap_or(false) }
658}
659
660unsafe extern "C" fn srv_set_weather(
661 _ctx: *mut c_void,
662 dim: YogStr,
663 raining: bool,
664 dur: i32,
665) -> bool {
666 unsafe { bridge!("worldSetWeather", [S(dim), Z(raining), I(dur)]).unwrap_or(false) }
667}
668
669unsafe extern "C" fn srv_give_item(
670 _ctx: *mut c_void,
671 player: YogStr,
672 item: YogStr,
673 count: u32,
674) -> bool {
675 unsafe { bridge!("giveItem", [S(player), S(item), I(count as i32)]).unwrap_or(false) }
676}
677
678unsafe extern "C" fn srv_player_teleport(_ctx: *mut c_void, player: YogStr, pos: YogVec3) -> bool {
679 unsafe { bridge!("teleport", [S(player), D(pos.x), D(pos.y), D(pos.z)]).unwrap_or(false) }
680}
681
682unsafe extern "C" fn srv_send_to_player(
683 _ctx: *mut c_void,
684 player: YogStr,
685 channel: YogStr,
686 data: *const u8,
687 len: u32,
688) -> bool {
689 unsafe {
690 bridge!(
691 "sendToPlayer",
692 [
693 S(player),
694 S(channel),
695 Bytes(std::slice::from_raw_parts(data, len as usize))
696 ]
697 )
698 .unwrap_or(false)
699 }
700}
701
702unsafe extern "C" fn srv_send_to_server(
703 _ctx: *mut c_void,
704 channel: YogStr,
705 data: *const u8,
706 len: u32,
707) -> bool {
708 unsafe {
709 host!(
710 "dev/yog/YogClient",
711 "sendToServer",
712 [
713 S(channel),
714 Bytes(std::slice::from_raw_parts(data, len as usize))
715 ]
716 )
717 .unwrap_or(false)
718 }
719}
720
721unsafe extern "C" fn srv_kick_player(_ctx: *mut c_void, player: YogStr, reason: YogStr) -> bool {
722 unsafe { bridge!("kickPlayer", [S(player), S(reason)]).unwrap_or(false) }
723}
724
725unsafe extern "C" fn srv_set_gamemode(_ctx: *mut c_void, player: YogStr, mode: YogStr) -> bool {
726 unsafe { bridge!("setGamemode", [S(player), S(mode)]).unwrap_or(false) }
727}
728
729unsafe extern "C" fn srv_send_title(
730 _ctx: *mut c_void,
731 player: YogStr,
732 title: YogStr,
733 sub: YogStr,
734 fi: i32,
735 stay: i32,
736 fo: i32,
737) -> bool {
738 unsafe {
739 bridge!(
740 "sendTitle",
741 [S(player), S(title), S(sub), I(fi), I(stay), I(fo)]
742 )
743 .unwrap_or(false)
744 }
745}
746
747unsafe extern "C" fn srv_send_actionbar(_ctx: *mut c_void, player: YogStr, msg: YogStr) -> bool {
748 unsafe { bridge!("sendActionbar", [S(player), S(msg)]).unwrap_or(false) }
749}
750
751unsafe extern "C" fn srv_play_sound(
752 _ctx: *mut c_void,
753 dim: YogStr,
754 pos: YogVec3,
755 sound: YogStr,
756 vol: f32,
757 pitch: f32,
758) -> bool {
759 unsafe {
760 bridge!(
761 "playSound",
762 [
763 S(dim),
764 D(pos.x),
765 D(pos.y),
766 D(pos.z),
767 S(sound),
768 F(vol),
769 F(pitch)
770 ]
771 )
772 .unwrap_or(false)
773 }
774}
775
776unsafe extern "C" fn srv_play_sound_player(
777 _ctx: *mut c_void,
778 player: YogStr,
779 sound: YogStr,
780 vol: f32,
781 pitch: f32,
782) -> bool {
783 unsafe {
784 bridge!("playSoundToPlayer", [S(player), S(sound), F(vol), F(pitch)]).unwrap_or(false)
785 }
786}
787
788unsafe extern "C" fn srv_entity_teleport(_ctx: *mut c_void, uuid: YogStr, pos: YogVec3) -> bool {
789 unsafe { bridge!("entityTeleport", [S(uuid), D(pos.x), D(pos.y), D(pos.z)]).unwrap_or(false) }
790}
791
792unsafe extern "C" fn srv_entity_rotation(
793 _ctx: *mut c_void,
794 uuid: YogStr,
795 out: *mut YogVec3,
796) -> bool {
797 unsafe {
798 let Some(s): Option<String> = bridge!("entityRotation", [S(uuid)]) else {
799 return false;
800 };
801 match parse_tsv::<f64, 2>(&s) {
802 Some([yaw, pitch]) => {
803 *out = YogVec3 {
804 x: yaw,
805 y: pitch,
806 z: 0.0,
807 };
808 true
809 }
810 None => false,
811 }
812 }
813}
814
815unsafe extern "C" fn srv_entity_position(
816 _ctx: *mut c_void,
817 uuid: YogStr,
818 out: *mut YogVec3,
819) -> bool {
820 unsafe {
821 let Some(s): Option<String> = bridge!("entityPosition", [S(uuid)]) else {
822 return false;
823 };
824 match parse_tsv::<f64, 3>(&s) {
825 Some([x, y, z]) => {
826 *out = YogVec3 { x, y, z };
827 true
828 }
829 None => false,
830 }
831 }
832}
833
834unsafe extern "C" fn srv_entity_health(_ctx: *mut c_void, uuid: YogStr, out: *mut f32) -> bool {
835 unsafe {
836 match bridge!("entityHealth", [S(uuid)]) {
837 Some(v) if !f64::is_nan(v) => {
838 *out = v as f32;
839 true
840 }
841 _ => false,
842 }
843 }
844}
845
846unsafe extern "C" fn srv_entity_set_health(_ctx: *mut c_void, uuid: YogStr, hp: f32) -> bool {
847 unsafe { bridge!("entitySetHealth", [S(uuid), D(hp as f64)]).unwrap_or(false) }
848}
849
850unsafe extern "C" fn srv_entity_kill(_ctx: *mut c_void, uuid: YogStr) -> bool {
851 unsafe { bridge!("entityKill", [S(uuid)]).unwrap_or(false) }
852}
853
854unsafe extern "C" fn srv_spawn_entity(
855 _ctx: *mut c_void,
856 type_id: YogStr,
857 dim: YogStr,
858 pos: YogVec3,
859) -> YogOwnedStr {
860 unsafe {
861 owned(bridge!(
862 "spawnEntity",
863 [S(type_id), S(dim), D(pos.x), D(pos.y), D(pos.z)]
864 ))
865 }
866}
867
868unsafe extern "C" fn srv_entity_add_effect(
869 _ctx: *mut c_void,
870 uuid: YogStr,
871 fx: YogStr,
872 dur: i32,
873 amp: u8,
874 particles: bool,
875) -> bool {
876 unsafe {
877 bridge!(
878 "entityAddEffect",
879 [S(uuid), S(fx), I(dur), I(amp as i32), Z(particles)]
880 )
881 .unwrap_or(false)
882 }
883}
884
885unsafe extern "C" fn srv_entity_remove_effect(_ctx: *mut c_void, uuid: YogStr, fx: YogStr) -> bool {
886 unsafe { bridge!("entityRemoveEffect", [S(uuid), S(fx)]).unwrap_or(false) }
887}
888
889unsafe extern "C" fn srv_entity_clear_effects(_ctx: *mut c_void, uuid: YogStr) -> bool {
890 unsafe { bridge!("entityClearEffects", [S(uuid)]).unwrap_or(false) }
891}
892
893unsafe extern "C" fn srv_entity_velocity(
894 _ctx: *mut c_void,
895 uuid: YogStr,
896 out: *mut YogVec3,
897) -> bool {
898 unsafe {
899 let Some(s): Option<String> = bridge!("entityVelocity", [S(uuid)]) else {
900 return false;
901 };
902 match parse_tsv::<f64, 3>(&s) {
903 Some([x, y, z]) => {
904 *out = YogVec3 { x, y, z };
905 true
906 }
907 None => false,
908 }
909 }
910}
911
912unsafe extern "C" fn srv_entity_set_velocity(
913 _ctx: *mut c_void,
914 uuid: YogStr,
915 vel: YogVec3,
916) -> bool {
917 unsafe {
918 bridge!("entitySetVelocity", [S(uuid), D(vel.x), D(vel.y), D(vel.z)]).unwrap_or(false)
919 }
920}
921
922unsafe extern "C" fn srv_entity_add_velocity(
923 _ctx: *mut c_void,
924 uuid: YogStr,
925 vel: YogVec3,
926) -> bool {
927 unsafe {
928 bridge!("entityAddVelocity", [S(uuid), D(vel.x), D(vel.y), D(vel.z)]).unwrap_or(false)
929 }
930}
931
932unsafe extern "C" fn srv_has_item_tag(_ctx: *mut c_void, item: YogStr, tag: YogStr) -> bool {
933 unsafe { bridge!("hasItemTag", [S(item), S(tag)]).unwrap_or(false) }
934}
935
936unsafe extern "C" fn srv_has_block_tag(_ctx: *mut c_void, block: YogStr, tag: YogStr) -> bool {
937 unsafe { bridge!("hasBlockTag", [S(block), S(tag)]).unwrap_or(false) }
938}
939
940unsafe extern "C" fn srv_drop_loot(
941 _ctx: *mut c_void,
942 table: YogStr,
943 dim: YogStr,
944 pos: YogVec3,
945) -> bool {
946 unsafe {
947 bridge!("dropLoot", [S(table), S(dim), D(pos.x), D(pos.y), D(pos.z)]).unwrap_or(false)
948 }
949}
950
951unsafe extern "C" fn srv_scoreboard_get(
952 _ctx: *mut c_void,
953 obj: YogStr,
954 player: YogStr,
955 out: *mut i32,
956) -> bool {
957 unsafe {
958 match bridge!("scoreboardGet", [S(obj), S(player)]) {
959 Some(v) if v != i32::MIN => {
960 *out = v;
961 true
962 }
963 _ => false,
964 }
965 }
966}
967
968unsafe extern "C" fn srv_scoreboard_set(
969 _ctx: *mut c_void,
970 obj: YogStr,
971 player: YogStr,
972 score: i32,
973) -> bool {
974 unsafe { bridge!("scoreboardSet", [S(obj), S(player), I(score)]).unwrap_or(false) }
975}
976
977unsafe extern "C" fn srv_scoreboard_add(
978 _ctx: *mut c_void,
979 obj: YogStr,
980 player: YogStr,
981 delta: i32,
982 out: *mut i32,
983) -> bool {
984 unsafe {
985 match bridge!("scoreboardAdd", [S(obj), S(player), I(delta)]) {
986 Some(v) if v != i32::MIN => {
987 *out = v;
988 true
989 }
990 _ => false,
991 }
992 }
993}
994
995unsafe extern "C" fn srv_bossbar_create(
996 _ctx: *mut c_void,
997 id: YogStr,
998 title: YogStr,
999 color: YogStr,
1000 style: YogStr,
1001) -> bool {
1002 unsafe { bridge!("bossbarCreate", [S(id), S(title), S(color), S(style)]).unwrap_or(false) }
1003}
1004
1005unsafe extern "C" fn srv_bossbar_remove(_ctx: *mut c_void, id: YogStr) -> bool {
1006 unsafe { bridge!("bossbarRemove", [S(id)]).unwrap_or(false) }
1007}
1008
1009unsafe extern "C" fn srv_bossbar_set_title(_ctx: *mut c_void, id: YogStr, title: YogStr) -> bool {
1010 unsafe { bridge!("bossbarSetTitle", [S(id), S(title)]).unwrap_or(false) }
1011}
1012
1013unsafe extern "C" fn srv_bossbar_set_progress(
1014 _ctx: *mut c_void,
1015 id: YogStr,
1016 progress: f32,
1017) -> bool {
1018 unsafe { bridge!("bossbarSetProgress", [S(id), F(progress)]).unwrap_or(false) }
1019}
1020
1021unsafe extern "C" fn srv_bossbar_set_color(_ctx: *mut c_void, id: YogStr, color: YogStr) -> bool {
1022 unsafe { bridge!("bossbarSetColor", [S(id), S(color)]).unwrap_or(false) }
1023}
1024
1025unsafe extern "C" fn srv_bossbar_add_player(_ctx: *mut c_void, id: YogStr, player: YogStr) -> bool {
1026 unsafe { bridge!("bossbarAddPlayer", [S(id), S(player)]).unwrap_or(false) }
1027}
1028
1029unsafe extern "C" fn srv_bossbar_remove_player(
1030 _ctx: *mut c_void,
1031 id: YogStr,
1032 player: YogStr,
1033) -> bool {
1034 unsafe { bridge!("bossbarRemovePlayer", [S(id), S(player)]).unwrap_or(false) }
1035}
1036
1037unsafe extern "C" fn srv_bossbar_set_visible(_ctx: *mut c_void, id: YogStr, visible: bool) -> bool {
1038 unsafe { bridge!("bossbarSetVisible", [S(id), Z(visible)]).unwrap_or(false) }
1039}
1040
1041unsafe extern "C" fn srv_get_block_nbt(
1042 _ctx: *mut c_void,
1043 dim: YogStr,
1044 pos: YogBlockPos,
1045) -> YogOwnedStr {
1046 unsafe {
1047 owned(bridge!(
1048 "getBlockNbt",
1049 [S(dim), I(pos.x), I(pos.y), I(pos.z)]
1050 ))
1051 }
1052}
1053
1054unsafe extern "C" fn srv_set_block_nbt(
1055 _ctx: *mut c_void,
1056 dim: YogStr,
1057 pos: YogBlockPos,
1058 snbt: YogStr,
1059) -> bool {
1060 unsafe {
1061 bridge!(
1062 "setBlockNbt",
1063 [S(dim), I(pos.x), I(pos.y), I(pos.z), S(snbt)]
1064 )
1065 .unwrap_or(false)
1066 }
1067}
1068
1069unsafe extern "C" fn srv_get_inventory_slot(
1070 _ctx: *mut c_void,
1071 dim: YogStr,
1072 pos: yog_abi::YogBlockPos,
1073 slot: u32,
1074) -> YogOwnedStr {
1075 unsafe {
1076 owned(bridge!(
1077 "getInventorySlot",
1078 [S(dim), I(pos.x), I(pos.y), I(pos.z), I(slot as i32)]
1079 ))
1080 }
1081}
1082
1083unsafe extern "C" fn srv_set_inventory_slot(
1084 _ctx: *mut c_void,
1085 dim: YogStr,
1086 pos: yog_abi::YogBlockPos,
1087 slot: u32,
1088 item_id: YogStr,
1089 count: u32,
1090) -> bool {
1091 unsafe {
1092 bridge!(
1093 "setInventorySlot",
1094 [
1095 S(dim),
1096 I(pos.x),
1097 I(pos.y),
1098 I(pos.z),
1099 I(slot as i32),
1100 S(item_id),
1101 I(count as i32)
1102 ]
1103 )
1104 .unwrap_or(false)
1105 }
1106}
1107
1108unsafe extern "C" fn srv_player_inventory(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
1109 unsafe { owned(bridge!("playerInventory", [S(player)])) }
1110}
1111
1112unsafe extern "C" fn srv_player_set_slot(
1113 _ctx: *mut c_void,
1114 player: YogStr,
1115 slot: u32,
1116 item_id: YogStr,
1117 count: u32,
1118) -> bool {
1119 unsafe {
1120 bridge!(
1121 "playerSetSlot",
1122 [S(player), I(slot as i32), S(item_id), I(count as i32)]
1123 )
1124 .unwrap_or(false)
1125 }
1126}
1127
1128unsafe extern "C" fn srv_player_teleport_dim(
1129 _ctx: *mut c_void,
1130 player: YogStr,
1131 dim: YogStr,
1132 pos: YogVec3,
1133) -> bool {
1134 unsafe {
1135 bridge!(
1136 "teleportToDim",
1137 [S(player), S(dim), D(pos.x), D(pos.y), D(pos.z)]
1138 )
1139 .unwrap_or(false)
1140 }
1141}
1142
1143unsafe extern "C" fn srv_entity_teleport_dim(
1144 _ctx: *mut c_void,
1145 uuid: YogStr,
1146 dim: YogStr,
1147 pos: YogVec3,
1148) -> bool {
1149 unsafe {
1150 bridge!(
1151 "entityTeleportToDim",
1152 [S(uuid), S(dim), D(pos.x), D(pos.y), D(pos.z)]
1153 )
1154 .unwrap_or(false)
1155 }
1156}
1157
1158unsafe extern "C" fn srv_online_players(_ctx: *mut c_void) -> YogOwnedStr {
1159 unsafe { owned(bridge!("onlinePlayers", [])) }
1160}
1161
1162unsafe extern "C" fn srv_world_entity_count(
1163 _ctx: *mut c_void,
1164 dim: YogStr,
1165 entity_type: YogStr,
1166) -> i32 {
1167 unsafe { bridge!("worldEntityCount", [S(dim), S(entity_type)]).unwrap_or(-1) }
1168}
1169
1170unsafe extern "C" fn srv_game_dir(_ctx: *mut c_void) -> YogOwnedStr {
1171 unsafe { owned(bridge!("gameDir", [])) }
1172}
1173
1174unsafe extern "C" fn srv_entity_get_nbt(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
1175 unsafe { owned(bridge!("entityGetNbt", [S(uuid)])) }
1176}
1177
1178unsafe extern "C" fn srv_entity_set_nbt(_ctx: *mut c_void, uuid: YogStr, snbt: YogStr) -> bool {
1179 unsafe { bridge!("entitySetNbt", [S(uuid), S(snbt)]).unwrap_or(false) }
1180}
1181
1182unsafe extern "C" fn srv_spawn_particles(
1183 _ctx: *mut c_void,
1184 dim: YogStr,
1185 pos: YogVec3,
1186 particle_type: YogStr,
1187 count: i32,
1188 dx: f64,
1189 dy: f64,
1190 dz: f64,
1191 speed: f64,
1192) -> bool {
1193 unsafe {
1194 bridge!(
1195 "spawnParticles",
1196 [
1197 S(dim),
1198 D(pos.x),
1199 D(pos.y),
1200 D(pos.z),
1201 S(particle_type),
1202 I(count),
1203 D(dx),
1204 D(dy),
1205 D(dz),
1206 D(speed)
1207 ]
1208 )
1209 .unwrap_or(false)
1210 }
1211}
1212
1213unsafe extern "C" fn srv_entity_attribute_get(
1214 _ctx: *mut c_void,
1215 uuid: YogStr,
1216 attr: YogStr,
1217) -> f64 {
1218 unsafe { bridge!("entityAttributeGet", [S(uuid), S(attr)]).unwrap_or(f64::NAN) }
1219}
1220
1221unsafe extern "C" fn srv_entity_attribute_set(
1222 _ctx: *mut c_void,
1223 uuid: YogStr,
1224 attr: YogStr,
1225 value: f64,
1226) -> bool {
1227 unsafe { bridge!("entityAttributeSet", [S(uuid), S(attr), D(value)]).unwrap_or(false) }
1228}
1229
1230unsafe extern "C" fn srv_get_held_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
1231 unsafe { owned(bridge!("getHeldItemNbt", [S(player)])) }
1232}
1233
1234unsafe extern "C" fn srv_set_held_item_nbt(
1235 _ctx: *mut c_void,
1236 player: YogStr,
1237 snbt: YogStr,
1238) -> bool {
1239 unsafe { bridge!("setHeldItemNbt", [S(player), S(snbt)]).unwrap_or(false) }
1240}
1241
1242unsafe extern "C" fn srv_get_offhand_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
1243 unsafe { owned(bridge!("getOffhandItemNbt", [S(player)])) }
1244}
1245
1246unsafe extern "C" fn srv_set_offhand_item_nbt(
1247 _ctx: *mut c_void,
1248 player: YogStr,
1249 snbt: YogStr,
1250) -> bool {
1251 unsafe { bridge!("setOffhandItemNbt", [S(player), S(snbt)]).unwrap_or(false) }
1252}
1253
1254unsafe extern "C" fn srv_get_slot_item(
1255 _ctx: *mut c_void,
1256 player: YogStr,
1257 slot: u32,
1258) -> YogOwnedStr {
1259 unsafe { owned(bridge!("getSlotItem", [S(player), I(slot as i32)])) }
1260}
1261
1262unsafe extern "C" fn srv_set_slot_item(
1263 _ctx: *mut c_void,
1264 player: YogStr,
1265 slot: u32,
1266 item_id: YogStr,
1267 count: u32,
1268 snbt: YogStr,
1269) -> bool {
1270 unsafe {
1271 bridge!(
1272 "setSlotItem",
1273 [
1274 S(player),
1275 I(slot as i32),
1276 S(item_id),
1277 I(count as i32),
1278 S(snbt)
1279 ]
1280 )
1281 .unwrap_or(false)
1282 }
1283}
1284
1285fn gl_draw_mode(mode: u8) -> u32 {
1294 match mode {
1295 1 => glow::LINES,
1296 2 => glow::LINE_STRIP,
1297 3 => glow::TRIANGLE_STRIP,
1298 4 => glow::TRIANGLE_FAN,
1299 _ => glow::TRIANGLES,
1300 }
1301}
1302
1303fn gl_attr_type(dtype: u8) -> u32 {
1304 match dtype {
1305 1 => glow::UNSIGNED_BYTE,
1306 2 => glow::INT,
1307 3 => glow::UNSIGNED_INT,
1308 _ => glow::FLOAT,
1309 }
1310}
1311
1312unsafe fn compile_shader(gl: &glow::Context, stage: u32, src: &str) -> Option<glow::NativeShader> {
1313 unsafe {
1314 let sh = gl.create_shader(stage).ok()?;
1315 gl.shader_source(sh, src);
1316 gl.compile_shader(sh);
1317 if !gl.get_shader_compile_status(sh) {
1318 yog_logging::error!("yog-gfx shader compile: {}", gl.get_shader_info_log(sh));
1319 gl.delete_shader(sh);
1320 return None;
1321 }
1322 Some(sh)
1323 }
1324}
1325
1326unsafe extern "C" fn gfx_buf_create() -> u32 {
1329 unsafe {
1330 let Some(g) = GL.get() else { return 0 };
1331 g.0.create_buffer().map(|b| b.0.get()).unwrap_or(0)
1332 }
1333}
1334
1335unsafe extern "C" fn gfx_buf_delete(handle: u32) {
1336 unsafe {
1337 let Some(g) = GL.get() else { return };
1338 let Some(n) = NonZeroU32::new(handle) else {
1339 return;
1340 };
1341 g.0.delete_buffer(glow::NativeBuffer(n));
1342 }
1343}
1344
1345unsafe extern "C" fn gfx_buf_data(handle: u32, bytes: *const u8, len: u32, dynamic: bool) {
1346 unsafe {
1347 let Some(g) = GL.get() else { return };
1348 let Some(n) = NonZeroU32::new(handle) else {
1349 return;
1350 };
1351 let gl = &g.0;
1352 let data = std::slice::from_raw_parts(bytes, len as usize);
1353 let usage = if dynamic {
1354 glow::DYNAMIC_DRAW
1355 } else {
1356 glow::STATIC_DRAW
1357 };
1358 gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
1359 gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, data, usage);
1360 gl.bind_buffer(glow::ARRAY_BUFFER, None);
1361 }
1362}
1363
1364unsafe extern "C" fn gfx_buf_subdata(handle: u32, offset: u32, bytes: *const u8, len: u32) {
1365 unsafe {
1366 let Some(g) = GL.get() else { return };
1367 let Some(n) = NonZeroU32::new(handle) else {
1368 return;
1369 };
1370 let gl = &g.0;
1371 let data = std::slice::from_raw_parts(bytes, len as usize);
1372 gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
1373 gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, offset as i32, data);
1374 gl.bind_buffer(glow::ARRAY_BUFFER, None);
1375 }
1376}
1377
1378unsafe extern "C" fn gfx_vao_create() -> u32 {
1381 unsafe {
1382 let Some(g) = GL.get() else { return 0 };
1383 g.0.create_vertex_array().map(|v| v.0.get()).unwrap_or(0)
1384 }
1385}
1386
1387unsafe extern "C" fn gfx_vao_delete(handle: u32) {
1388 unsafe {
1389 let Some(g) = GL.get() else { return };
1390 let Some(n) = NonZeroU32::new(handle) else {
1391 return;
1392 };
1393 g.0.delete_vertex_array(glow::NativeVertexArray(n));
1394 }
1395}
1396
1397unsafe extern "C" fn gfx_vao_attrib(
1398 vao: u32,
1399 vbo: u32,
1400 index: u32,
1401 components: u8,
1402 dtype: u8,
1403 normalized: bool,
1404 stride: u32,
1405 offset: u32,
1406) {
1407 unsafe {
1408 let Some(g) = GL.get() else { return };
1409 let (Some(vn), Some(bn)) = (NonZeroU32::new(vao), NonZeroU32::new(vbo)) else {
1410 return;
1411 };
1412 let gl = &g.0;
1413 gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
1414 gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(bn)));
1415 let gl_type = gl_attr_type(dtype);
1416 if dtype == 2 || dtype == 3 {
1417 gl.vertex_attrib_pointer_i32(
1418 index,
1419 components as i32,
1420 gl_type,
1421 stride as i32,
1422 offset as i32,
1423 );
1424 } else {
1425 gl.vertex_attrib_pointer_f32(
1426 index,
1427 components as i32,
1428 gl_type,
1429 normalized,
1430 stride as i32,
1431 offset as i32,
1432 );
1433 }
1434 gl.enable_vertex_attrib_array(index);
1435 gl.bind_vertex_array(None);
1436 }
1437}
1438
1439unsafe extern "C" fn gfx_vao_set_ebo(vao: u32, ebo: u32) {
1440 unsafe {
1441 let Some(g) = GL.get() else { return };
1442 let (Some(vn), Some(en)) = (NonZeroU32::new(vao), NonZeroU32::new(ebo)) else {
1443 return;
1444 };
1445 let gl = &g.0;
1446 gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
1447 gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(glow::NativeBuffer(en)));
1448 gl.bind_vertex_array(None);
1449 }
1450}
1451
1452fn shader_cache_path(vert: &str, frag: &str) -> Option<PathBuf> {
1457 use std::hash::{Hash, Hasher};
1458 let mut h = std::collections::hash_map::DefaultHasher::new();
1459 vert.hash(&mut h);
1460 frag.hash(&mut h);
1461 let hash = h.finish();
1462 let dir = std::env::var("HOME")
1463 .map(|home| {
1464 PathBuf::from(home)
1465 .join(".cache")
1466 .join("yog")
1467 .join("shaders")
1468 })
1469 .ok()?;
1470 std::fs::create_dir_all(&dir).ok()?;
1471 Some(dir.join(format!("{hash:016x}.ysc")))
1472}
1473
1474unsafe fn load_shader_binary(gl: &glow::Context, data: &[u8]) -> Option<glow::NativeProgram> {
1480 unsafe {
1481 if data.len() < 4 {
1482 return None;
1483 }
1484 type ProgramBinaryFn = unsafe extern "system" fn(u32, u32, *const c_void, i32);
1485 let fn_ptr = (*GL_PROGRAM_BINARY.get()?)?;
1486 let program_binary: ProgramBinaryFn = std::mem::transmute(fn_ptr);
1487
1488 let fmt = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1489 let prog = gl.create_program().ok()?;
1490 program_binary(
1491 prog.0.get(),
1492 fmt,
1493 data[4..].as_ptr() as *const c_void,
1494 (data.len() - 4) as i32,
1495 );
1496 if gl.get_program_link_status(prog) {
1497 Some(prog)
1498 } else {
1499 gl.delete_program(prog);
1500 None
1501 }
1502 }
1503}
1504
1505unsafe fn get_shader_binary(prog: glow::NativeProgram) -> (Vec<u8>, u32) {
1507 unsafe {
1508 type GetProgramivFn = unsafe extern "system" fn(u32, u32, *mut i32);
1509 type GetProgramBinaryFn =
1510 unsafe extern "system" fn(u32, i32, *mut i32, *mut u32, *mut c_void);
1511
1512 let Some(get_iv_raw) = GL_GET_PROGRAM_IV.get().and_then(|v| *v) else {
1513 return (vec![], 0);
1514 };
1515 let Some(get_bin_raw) = GL_GET_PROGRAM_BINARY.get().and_then(|v| *v) else {
1516 return (vec![], 0);
1517 };
1518 let get_program_iv: GetProgramivFn = std::mem::transmute(get_iv_raw);
1519 let get_program_binary: GetProgramBinaryFn = std::mem::transmute(get_bin_raw);
1520
1521 const PROGRAM_BINARY_LENGTH: u32 = 0x8741;
1523 let mut size: i32 = 0;
1524 get_program_iv(prog.0.get(), PROGRAM_BINARY_LENGTH, &mut size);
1525 if size <= 0 {
1526 return (vec![], 0);
1527 }
1528
1529 let mut buf = vec![0u8; size as usize];
1530 let mut actual_len: i32 = 0;
1531 let mut fmt: u32 = 0;
1532 get_program_binary(
1533 prog.0.get(),
1534 size,
1535 &mut actual_len,
1536 &mut fmt,
1537 buf.as_mut_ptr() as *mut c_void,
1538 );
1539 buf.truncate(actual_len.max(0) as usize);
1540 (buf, fmt)
1541 }
1542}
1543
1544unsafe extern "C" fn gfx_prog_create(vert: YogStr, frag: YogStr, out: *mut u32) -> bool {
1545 unsafe {
1546 let Some(g) = GL.get() else { return false };
1547 let gl = &g.0;
1548 let vert_s = vert.as_str();
1549 let frag_s = frag.as_str();
1550
1551 let cache_path = shader_cache_path(vert_s, frag_s);
1553 if let Some(ref path) = cache_path
1554 && let Ok(data) = std::fs::read(path)
1555 {
1556 if let Some(prog) = load_shader_binary(gl, &data) {
1557 *out = prog.0.get();
1558 return true;
1559 }
1560 let _ = std::fs::remove_file(path);
1562 }
1563
1564 let vs = match compile_shader(gl, glow::VERTEX_SHADER, vert_s) {
1566 Some(s) => s,
1567 None => return false,
1568 };
1569 let fs = match compile_shader(gl, glow::FRAGMENT_SHADER, frag_s) {
1570 Some(s) => s,
1571 None => {
1572 gl.delete_shader(vs);
1573 return false;
1574 }
1575 };
1576 let prog = match gl.create_program() {
1577 Ok(p) => p,
1578 Err(e) => {
1579 yog_logging::error!("yog-gfx: create_program: {}", e);
1580 gl.delete_shader(vs);
1581 gl.delete_shader(fs);
1582 return false;
1583 }
1584 };
1585 gl.attach_shader(prog, vs);
1586 gl.attach_shader(prog, fs);
1587 gl.link_program(prog);
1588 gl.detach_shader(prog, vs);
1589 gl.detach_shader(prog, fs);
1590 gl.delete_shader(vs);
1591 gl.delete_shader(fs);
1592 if !gl.get_program_link_status(prog) {
1593 yog_logging::error!("yog-gfx: shader link: {}", gl.get_program_info_log(prog));
1594 gl.delete_program(prog);
1595 return false;
1596 }
1597
1598 if let Some(ref path) = cache_path {
1600 let (binary, fmt) = get_shader_binary(prog);
1601 if !binary.is_empty() {
1602 let mut blob = fmt.to_le_bytes().to_vec();
1603 blob.extend_from_slice(&binary);
1604 let _ = std::fs::write(path, &blob);
1605 }
1606 }
1607
1608 *out = prog.0.get();
1609 true
1610 }
1611}
1612
1613unsafe extern "C" fn gfx_prog_delete(handle: u32) {
1614 unsafe {
1615 let Some(g) = GL.get() else { return };
1616 let Some(n) = NonZeroU32::new(handle) else {
1617 return;
1618 };
1619 g.0.delete_program(glow::NativeProgram(n));
1620 }
1621}
1622
1623macro_rules! with_prog {
1624 ($handle:expr_2021, |$gl:ident, $prog:ident, $loc:ident ($name:expr_2021)| $body:expr_2021) => {{
1625 let Some(g) = GL.get() else { return };
1626 let Some(n) = NonZeroU32::new($handle) else {
1627 return;
1628 };
1629 let $gl = &g.0;
1630 let $prog = glow::NativeProgram(n);
1631 $gl.use_program(Some($prog));
1632 let $loc = $gl.get_uniform_location($prog, $name.as_str());
1633 $body
1634 }};
1635}
1636
1637unsafe extern "C" fn gfx_prog_uniform_1i(prog: u32, name: YogStr, v: i32) {
1638 unsafe {
1639 with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_i32(loc.as_ref(), v));
1640 }
1641}
1642unsafe extern "C" fn gfx_prog_uniform_1f(prog: u32, name: YogStr, v: f32) {
1643 unsafe {
1644 with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_f32(loc.as_ref(), v));
1645 }
1646}
1647unsafe extern "C" fn gfx_prog_uniform_2f(prog: u32, name: YogStr, x: f32, y: f32) {
1648 unsafe {
1649 with_prog!(prog, |gl, _p, loc(name)| gl.uniform_2_f32(
1650 loc.as_ref(),
1651 x,
1652 y
1653 ));
1654 }
1655}
1656unsafe extern "C" fn gfx_prog_uniform_3f(prog: u32, name: YogStr, x: f32, y: f32, z: f32) {
1657 unsafe {
1658 with_prog!(prog, |gl, _p, loc(name)| gl.uniform_3_f32(
1659 loc.as_ref(),
1660 x,
1661 y,
1662 z
1663 ));
1664 }
1665}
1666unsafe extern "C" fn gfx_prog_uniform_4f(prog: u32, name: YogStr, x: f32, y: f32, z: f32, w: f32) {
1667 unsafe {
1668 with_prog!(prog, |gl, _p, loc(name)| gl.uniform_4_f32(
1669 loc.as_ref(),
1670 x,
1671 y,
1672 z,
1673 w
1674 ));
1675 }
1676}
1677unsafe extern "C" fn gfx_prog_uniform_mat4(prog: u32, name: YogStr, col_major: *const f32) {
1678 unsafe {
1679 with_prog!(prog, |gl, _p, loc(name)| {
1680 let data = std::slice::from_raw_parts(col_major, 16);
1681 gl.uniform_matrix_4_f32_slice(loc.as_ref(), false, data);
1682 });
1683 }
1684}
1685
1686unsafe extern "C" fn gfx_tex_create(w: u32, h: u32, rgba: *const u8, linear: bool) -> u32 {
1689 unsafe {
1690 let Some(g) = GL.get() else { return 0 };
1691 let gl = &g.0;
1692 let tex = match gl.create_texture() {
1693 Ok(t) => t,
1694 Err(_) => return 0,
1695 };
1696 gl.bind_texture(glow::TEXTURE_2D, Some(tex));
1697 let filter = if linear { glow::LINEAR } else { glow::NEAREST };
1698 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, filter as i32);
1699 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, filter as i32);
1700 gl.tex_parameter_i32(
1701 glow::TEXTURE_2D,
1702 glow::TEXTURE_WRAP_S,
1703 glow::CLAMP_TO_EDGE as i32,
1704 );
1705 gl.tex_parameter_i32(
1706 glow::TEXTURE_2D,
1707 glow::TEXTURE_WRAP_T,
1708 glow::CLAMP_TO_EDGE as i32,
1709 );
1710 let pixels = std::slice::from_raw_parts(rgba, (w * h * 4) as usize);
1711 unpack_tightly(gl);
1714 gl.tex_image_2d(
1715 glow::TEXTURE_2D,
1716 0,
1717 glow::RGBA8 as i32,
1718 w as i32,
1719 h as i32,
1720 0,
1721 glow::RGBA,
1722 glow::UNSIGNED_BYTE,
1723 glow::PixelUnpackData::Slice(Some(pixels)),
1724 );
1725 gl.bind_texture(glow::TEXTURE_2D, None);
1726 tex.0.get()
1727 }
1728}
1729
1730unsafe fn unpack_tightly(gl: &glow::Context) {
1732 unsafe {
1733 gl.bind_buffer(glow::PIXEL_UNPACK_BUFFER, None);
1734 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
1735 gl.pixel_store_i32(glow::UNPACK_SKIP_ROWS, 0);
1736 gl.pixel_store_i32(glow::UNPACK_SKIP_PIXELS, 0);
1737 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 4);
1738 }
1739}
1740
1741unsafe extern "C" fn gfx_tex_delete(handle: u32) {
1742 unsafe {
1743 let Some(g) = GL.get() else { return };
1744 let Some(n) = NonZeroU32::new(handle) else {
1745 return;
1746 };
1747 g.0.delete_texture(glow::NativeTexture(n));
1748 }
1749}
1750
1751unsafe extern "C" fn gfx_tex_bind(unit: u32, handle: u32) {
1752 unsafe {
1753 let Some(g) = GL.get() else { return };
1754 let gl = &g.0;
1755 gl.active_texture(glow::TEXTURE0 + unit);
1756 match NonZeroU32::new(handle) {
1757 Some(n) => gl.bind_texture(glow::TEXTURE_2D, Some(glow::NativeTexture(n))),
1758 None => gl.bind_texture(glow::TEXTURE_2D, None),
1759 }
1760 }
1761}
1762
1763unsafe extern "C" fn gfx_tex_from_mc(id: YogStr) -> u32 {
1764 unsafe { host!("dev/yog/NativeDraw", "getMcTextureId", [S(id)]).map_or(0, |id: i32| id as u32) }
1765}
1766
1767unsafe extern "C" fn gfx_draw_arrays(vao: u32, prog: u32, mode: u8, first: u32, count: u32) {
1770 unsafe {
1771 let Some(g) = GL.get() else { return };
1772 let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else {
1773 return;
1774 };
1775 let gl = &g.0;
1776 gl.use_program(Some(glow::NativeProgram(pn)));
1777 gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
1778 gl.draw_arrays(gl_draw_mode(mode), first as i32, count as i32);
1779 gl.bind_vertex_array(None);
1780 }
1781}
1782
1783unsafe extern "C" fn gfx_draw_elements(
1784 vao: u32,
1785 ebo: u32,
1786 prog: u32,
1787 mode: u8,
1788 count: u32,
1789 u32_idx: bool,
1790) {
1791 unsafe {
1792 let Some(g) = GL.get() else { return };
1793 let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else {
1794 return;
1795 };
1796 let gl = &g.0;
1797 gl.use_program(Some(glow::NativeProgram(pn)));
1798 gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
1799 let _ = ebo;
1801 let idx_type = if u32_idx {
1802 glow::UNSIGNED_INT
1803 } else {
1804 glow::UNSIGNED_SHORT
1805 };
1806 gl.draw_elements(gl_draw_mode(mode), count as i32, idx_type, 0);
1807 gl.bind_vertex_array(None);
1808 }
1809}
1810
1811unsafe extern "C" fn gfx_set_blend(enabled: bool, src: u32, dst: u32) {
1814 unsafe {
1815 let Some(g) = GL.get() else { return };
1816 let gl = &g.0;
1817 if enabled {
1818 gl.enable(glow::BLEND);
1819 gl.blend_func(src, dst);
1820 } else {
1821 gl.disable(glow::BLEND);
1822 }
1823 }
1824}
1825
1826unsafe extern "C" fn gfx_set_depth(test: bool, write: bool) {
1827 unsafe {
1828 let Some(g) = GL.get() else { return };
1829 let gl = &g.0;
1830 if test {
1831 gl.enable(glow::DEPTH_TEST);
1832 } else {
1833 gl.disable(glow::DEPTH_TEST);
1834 }
1835 gl.depth_mask(write);
1836 if !test {
1838 gl.disable(glow::CULL_FACE);
1839 }
1840 }
1841}
1842
1843unsafe extern "C" fn gfx_set_scissor(x: i32, y: i32, w: i32, h: i32) {
1844 unsafe {
1845 let Some(g) = GL.get() else { return };
1846 let gl = &g.0;
1847 gl.enable(glow::SCISSOR_TEST);
1848 gl.scissor(x, y, w, h);
1849 }
1850}
1851
1852unsafe extern "C" fn gfx_clear_scissor() {
1853 unsafe {
1854 let Some(g) = GL.get() else { return };
1855 g.0.disable(glow::SCISSOR_TEST);
1856 }
1857}
1858
1859unsafe extern "C" fn gfx_set_viewport(x: i32, y: i32, w: i32, h: i32) {
1860 unsafe {
1861 let Some(g) = GL.get() else { return };
1862 g.0.viewport(x, y, w, h);
1863 }
1864}
1865
1866unsafe extern "C" fn gfx_draw2d_rect(x1: f32, y1: f32, x2: f32, y2: f32, color: u32) {
1869 unsafe {
1870 let _: Option<()> = host!(
1871 "dev/yog/NativeDraw",
1872 "drawRect",
1873 [F(x1), F(y1), F(x2), F(y2), I(color as i32)]
1874 );
1875 }
1876}
1877
1878unsafe extern "C" fn gfx_draw2d_gradient(
1879 x1: f32,
1880 y1: f32,
1881 x2: f32,
1882 y2: f32,
1883 top: u32,
1884 bottom: u32,
1885) {
1886 unsafe {
1887 let _: Option<()> = host!(
1888 "dev/yog/NativeDraw",
1889 "drawGradientRect",
1890 [F(x1), F(y1), F(x2), F(y2), I(top as i32), I(bottom as i32)]
1891 );
1892 }
1893}
1894
1895unsafe extern "C" fn gfx_draw2d_text(text: YogStr, x: f32, y: f32, color: u32, shadow: bool) {
1896 unsafe {
1897 let _: Option<()> = host!(
1898 "dev/yog/NativeDraw",
1899 "drawText",
1900 [S(text), F(x), F(y), I(color as i32), Z(shadow)]
1901 );
1902 }
1903}
1904
1905unsafe extern "C" fn gfx_draw2d_text_font(
1906 text: YogStr,
1907 font: YogStr,
1908 x: f32,
1909 y: f32,
1910 color: u32,
1911 shadow: bool,
1912) {
1913 unsafe {
1914 let _: Option<()> = host!(
1915 "dev/yog/NativeDraw",
1916 "drawTextFont",
1917 [S(text), S(font), F(x), F(y), I(color as i32), Z(shadow)]
1918 );
1919 }
1920}
1921
1922unsafe extern "C" fn gfx_draw2d_text_scaled(
1923 text: YogStr,
1924 font: YogStr,
1925 x: f32,
1926 y: f32,
1927 scale: f32,
1928 color: u32,
1929 shadow: bool,
1930) {
1931 unsafe {
1932 let _: Option<()> = host!(
1933 "dev/yog/NativeDraw",
1934 "drawTextScaled",
1935 [S(text), S(font), F(x), F(y), F(scale), I(color as i32), Z(shadow)]
1936 );
1937 }
1938}
1939
1940unsafe extern "C" fn gfx_draw2d_blocks(blocks: YogStr, x: f32, y: f32, w: f32, h: f32, yaw: f32, pitch: f32) {
1941 if w <= 0.0 || h <= 0.0 {
1942 return;
1943 }
1944 unsafe {
1945 let _: Option<()> = host!(
1946 "dev/yog/NativeDraw",
1947 "drawBlocks",
1948 [S(blocks), F(x), F(y), F(w), F(h), F(yaw), F(pitch)]
1949 );
1950 }
1951}
1952
1953unsafe extern "C" fn gfx_text_width(text: YogStr, font: YogStr) -> f32 {
1954 unsafe { host!("dev/yog/NativeDraw", "textWidth", [S(text), S(font)]).unwrap_or(0.0) }
1955}
1956
1957unsafe extern "C" fn gfx_draw2d_item(id: YogStr, x: f32, y: f32, size: f32) {
1958 unsafe {
1959 let _: Option<()> = host!(
1960 "dev/yog/NativeDraw",
1961 "drawItem",
1962 [S(id), F(x), F(y), F(size)]
1963 );
1964 }
1965}
1966
1967static GFX_FN_TABLE: YogGfxApi = YogGfxApi {
1970 screen_w: 0,
1972 screen_h: 0,
1973 delta_tick: 0.0,
1974 scale_factor: 1.0,
1975 view_proj: [0.0; 16],
1976 camera_pos: [0.0; 3],
1977 player_pos: [0.0; 3],
1978 _pad1: 0.0,
1979 buf_create: gfx_buf_create,
1980 buf_delete: gfx_buf_delete,
1981 buf_data: gfx_buf_data,
1982 buf_subdata: gfx_buf_subdata,
1983 vao_create: gfx_vao_create,
1984 vao_delete: gfx_vao_delete,
1985 vao_attrib: gfx_vao_attrib,
1986 vao_set_ebo: gfx_vao_set_ebo,
1987 prog_create: gfx_prog_create,
1988 prog_delete: gfx_prog_delete,
1989 prog_uniform_1i: gfx_prog_uniform_1i,
1990 prog_uniform_1f: gfx_prog_uniform_1f,
1991 prog_uniform_2f: gfx_prog_uniform_2f,
1992 prog_uniform_3f: gfx_prog_uniform_3f,
1993 prog_uniform_4f: gfx_prog_uniform_4f,
1994 prog_uniform_mat4: gfx_prog_uniform_mat4,
1995 tex_create: gfx_tex_create,
1996 tex_delete: gfx_tex_delete,
1997 tex_bind: gfx_tex_bind,
1998 tex_from_mc: gfx_tex_from_mc,
1999 draw_arrays: gfx_draw_arrays,
2000 draw_elements: gfx_draw_elements,
2001 set_blend: gfx_set_blend,
2002 set_depth: gfx_set_depth,
2003 set_scissor: gfx_set_scissor,
2004 clear_scissor: gfx_clear_scissor,
2005 set_viewport: gfx_set_viewport,
2006 draw2d_rect: gfx_draw2d_rect,
2007 draw2d_gradient: gfx_draw2d_gradient,
2008 draw2d_text: gfx_draw2d_text,
2009 draw2d_item: gfx_draw2d_item,
2010 draw2d_text_font: gfx_draw2d_text_font,
2011 text_width: gfx_text_width,
2012 draw2d_image: images::gfx_draw2d_image,
2013 image_size: images::gfx_image_size,
2014 draw2d_svg: svg::gfx_draw2d_svg,
2015 draw2d_entity: svg::gfx_draw2d_entity,
2016 draw2d_text_scaled: gfx_draw2d_text_scaled,
2017 draw2d_blocks: gfx_draw2d_blocks,
2018};
2019
2020macro_rules! api_event {
2027 ($name:ident, $field:ident, $fn_ty:ty) => {
2028 unsafe extern "C" fn $name(ctx: *mut c_void, ud: *mut c_void, h: $fn_ty) {
2029 unsafe {
2030 let mut handlers = handlers_mut(ctx);
2031 handlers.$field.push((current_registration(), ud, h));
2032 }
2033 }
2034 };
2035}
2036
2037api_event!(api_on_block_break, block_break, YogBlockBreakFn);
2038api_event!(api_on_chat, chat, YogChatFn);
2039api_event!(api_on_player_join, player_join, YogPlayerFn);
2040api_event!(api_on_player_leave, player_leave, YogPlayerFn);
2041api_event!(api_on_use_item, use_item, YogUseItemFn);
2042api_event!(api_on_use_block, use_block, YogUseBlockFn);
2043api_event!(api_on_attack_entity, attack_entity, YogAttackEntityFn);
2044api_event!(api_on_entity_damage, entity_damage, YogEntityDamageFn);
2045api_event!(api_on_entity_death, entity_death, YogEntityDeathFn);
2046api_event!(api_on_entity_spawn, entity_spawn, YogEntitySpawnFn);
2047api_event!(
2048 api_on_player_place_block,
2049 player_place_block,
2050 YogPlaceBlockFn
2051);
2052api_event!(api_on_player_death, player_death, YogPlayerDeathFn);
2053api_event!(api_on_player_respawn, player_respawn, YogPlayerRespawnFn);
2054api_event!(api_on_advancement, advancement, YogAdvancementFn);
2055api_event!(api_on_entity_interact, entity_interact, YogEntityInteractFn);
2056api_event!(api_on_item_craft, item_craft, YogCraftFn);
2057api_event!(api_on_machine_craft, machine_craft, YogMachineCraftFn);
2058api_event!(api_on_explosion, explosion, YogExplosionFn);
2059api_event!(api_on_item_pickup, item_pickup, YogItemPickupFn);
2060api_event!(api_on_item_insert, item_insert, YogItemInsertFn);
2061api_event!(api_on_player_move, player_move, YogPlayerMoveFn);
2062api_event!(api_on_container_open, container_open, YogContainerOpenFn);
2063api_event!(api_on_container_close, container_close, YogContainerCloseFn);
2064api_event!(api_on_projectile_hit, projectile_hit, YogProjectileHitFn);
2065api_event!(api_on_client_tick, client_tick, YogClientFn);
2066api_event!(api_on_hud_render, hud_render, YogHudRenderFn);
2067api_event!(api_on_world_render, world_render, YogWorldRenderFn);
2068api_event!(api_on_key_press, key_press, YogKeyPressFn);
2069api_event!(api_on_screen_open, screen_open, YogScreenFn);
2070api_event!(api_on_screen_close, screen_close, YogScreenFn);
2071api_event!(api_on_server_tick, server_tick, YogServerFn);
2072api_event!(api_on_server_started, server_started, YogServerFn);
2073api_event!(api_on_server_stopping, server_stopping, YogServerFn);
2074
2075unsafe extern "C" fn api_on_packet(
2076 ctx: *mut c_void,
2077 channel: YogStr,
2078 ud: *mut c_void,
2079 h: YogPacketFn,
2080) {
2081 unsafe {
2082 let mut handlers = handlers_mut(ctx);
2083 handlers
2084 .packets
2085 .insert(channel.as_str().to_owned(), (current_registration(), ud, h));
2086 }
2087}
2088
2089unsafe extern "C" fn api_on_client_packet(
2090 ctx: *mut c_void,
2091 channel: YogStr,
2092 ud: *mut c_void,
2093 h: YogPacketFn,
2094) {
2095 unsafe {
2096 let mut handlers = handlers_mut(ctx);
2097 handlers
2098 .client_packets
2099 .insert(channel.as_str().to_owned(), (current_registration(), ud, h));
2100 }
2101}
2102
2103unsafe extern "C" fn api_register_command(
2104 ctx: *mut c_void,
2105 name: YogStr,
2106 ud: *mut c_void,
2107 h: YogCommandFn,
2108) {
2109 unsafe {
2110 let mut handlers = handlers_mut(ctx);
2111 handlers
2112 .commands
2113 .entry(name.as_str().to_owned())
2114 .or_default()
2115 .push((current_registration(), None, ud, h));
2116 }
2117}
2118
2119unsafe extern "C" fn api_register_typed_command(
2120 ctx: *mut c_void,
2121 name: YogStr,
2122 schema: YogStr,
2123 ud: *mut c_void,
2124 h: YogCommandFn,
2125) {
2126 unsafe {
2127 let mut handlers = handlers_mut(ctx);
2128 let n = name.as_str().to_owned();
2129 handlers.commands.entry(n).or_default().push((
2130 current_registration(),
2131 Some(schema.as_str().to_owned()),
2132 ud,
2133 h,
2134 ));
2135 }
2136}
2137
2138unsafe fn handlers_mut<'a>(ctx: *mut c_void) -> std::sync::RwLockWriteGuard<'a, RuntimeHandlers> {
2146 unsafe { (*(ctx as *const RwLock<RuntimeHandlers>)).write().unwrap_or_else(|e| e.into_inner()) }
2147}
2148
2149unsafe extern "C" fn api_register_content(ctx: *mut c_void, kind: YogStr, id: YogStr, data: *const u8, len: usize) {
2152 unsafe {
2153 let kind = kind.as_str();
2154 let id = id.as_str().to_owned();
2155 let bytes = if data.is_null() || len == 0 { &[][..] } else { std::slice::from_raw_parts(data, len) };
2156 let arena = yog_data::Bump::new();
2158 let value = match yog_data::binary::decode_ref(bytes, &arena) {
2159 Ok(v) => v,
2160 Err(e) => {
2161 yog_logging::warn!("{kind} {id}: not Yog Data: {e}");
2162 return;
2163 }
2164 };
2165 register_content(&mut handlers_mut(ctx), kind, id, &value);
2166 }
2167}
2168
2169fn register_content(h: &mut RuntimeHandlers, kind: &str, id: String, value: &yog_data::ValueRef) {
2171 *h.content_blob.get_mut().unwrap_or_else(|e| e.into_inner()) = None;
2172 fn read<T: yog_data::Schema>(kind: &str, id: &str, v: &yog_data::ValueRef) -> Option<T> {
2173 match yog_data::read_ref_value::<T>(v) {
2174 Ok(value) => Some(value),
2175 Err(problems) => {
2176 let list: Vec<String> = problems.iter().map(|p| format!("{}: {}", p.path, p.message)).collect();
2177 yog_logging::warn!("{kind} {id}: {}", list.join("; "));
2178 None
2179 }
2180 }
2181 }
2182 let none_if_empty = |s: &mut Option<String>| {
2183 if s.as_deref() == Some("") {
2184 *s = None;
2185 }
2186 };
2187 match kind {
2188 "item" => {
2189 if let Some(mut d) = read::<ItemDef>(kind, &id, value) {
2190 none_if_empty(&mut d.name);
2191 none_if_empty(&mut d.tooltip);
2192 h.items.push(d);
2193 }
2194 }
2195 "block" => {
2196 if let Some(mut d) = read::<BlockDef>(kind, &id, value) {
2197 none_if_empty(&mut d.name);
2198 none_if_empty(&mut d.sound);
2199 none_if_empty(&mut d.inventory_id);
2200 h.blocks.push(d);
2201 }
2202 }
2203 "biome_feature" => {
2204 if let Some(f) = read::<yog_registry::BiomeFeature>(kind, &id, value) {
2205 h.biome_features.push(f);
2206 }
2207 }
2208 "tool_material" => {
2209 if let Some(m) = read::<yog_registry::ToolMaterial>(kind, &id, value) {
2210 h.tool_materials.insert(m.id.clone(), m);
2211 }
2212 }
2213 "armor_material" => {
2214 if let Some(m) = read::<yog_registry::ArmorMaterial>(kind, &id, value) {
2215 h.armor_materials.insert(m.id.clone(), m);
2216 }
2217 }
2218 "sound_group" => {
2219 if let Some(g) = read::<yog_registry::SoundGroup>(kind, &id, value) {
2220 h.sound_groups.insert(g.id.clone(), g);
2221 }
2222 }
2223 "inventory" => {
2224 if let Some(d) = read::<yog_inventory::InventoryDef>(kind, &id, value) {
2225 h.inventories.push(d);
2226 }
2227 }
2228 "startup_grant" => {
2229 if let Some(mut g) = read::<yog_registry::StartupGrant>(kind, &id, value) {
2230 none_if_empty(&mut g.book);
2231 none_if_empty(&mut g.command);
2232 h.startup_grants.push(g);
2233 }
2234 }
2235 "recipe" => {
2236 let (ns, name) = id.split_once(':').unwrap_or(("minecraft", &id));
2237 let recipe = value.to_value();
2238 for r in h.book_renderers.lock().unwrap().values_mut() {
2240 r.add_recipe(format!("{ns}:{name}"), &recipe);
2241 }
2242 h.recipes.push((ns.to_string(), name.to_string(), recipe));
2243 }
2244 "book" => {
2245 if let Some(mut book) = read::<yog_book::Book>(kind, &id, value) {
2247 book.entries.retain(|e| e.requires.iter().all(|m| mod_loaded(m)));
2249 h.books.insert(id.clone());
2250 let mut renderer = yog_book::BookRenderer::new(book);
2251 renderer.set_translator(translate);
2253 for (ns, name, recipe) in &h.recipes {
2255 renderer.add_recipe(format!("{ns}:{name}"), recipe);
2256 }
2257 h.book_renderers.get_mut().unwrap_or_else(|e| e.into_inner()).insert(id, renderer);
2258 }
2259 }
2260 "dimension" => {
2261 yog_logging::info!("registered dimension type: {}", id);
2262 let mut def = value.to_value();
2263 if let Some(t) = value.get("terrain") {
2265 let Some(terrain) = read::<yog_worldgen::Terrain>(kind, &id, t) else { return };
2266 if let yog_data::Value::Map(m) = &mut def {
2267 m.remove("terrain");
2268 m.insert("generator_type", yog_data::Value::from(id.as_str()));
2269 }
2270 h.terrains.insert(id.clone(), Arc::new(yog_worldgen::Generator::new(terrain)));
2271 }
2272 h.dimensions.insert(id, def);
2273 }
2274 other => yog_logging::warn!("unknown kind of content `{other}` ({id}) — this runtime doesn't know it"),
2275 }
2276}
2277
2278
2279
2280
2281unsafe extern "C" fn api_schedule_once(
2282 ctx: *mut c_void,
2283 delay_ticks: u64,
2284 ud: *mut c_void,
2285 h: YogScheduledFn,
2286) {
2287 unsafe {
2288 let handlers = handlers_mut(ctx);
2289 handlers
2290 .scheduler
2291 .lock()
2292 .expect("scheduler poisoned")
2293 .once_tasks
2294 .push(OnceTask {
2295 module: current_registration(),
2296 delay_remaining: delay_ticks,
2297 ud,
2298 f: h,
2299 });
2300 }
2301}
2302
2303unsafe extern "C" fn api_schedule_repeating(
2304 ctx: *mut c_void,
2305 period_ticks: u64,
2306 ud: *mut c_void,
2307 h: YogScheduledFn,
2308) {
2309 unsafe {
2310 let handlers = handlers_mut(ctx);
2311 handlers
2312 .scheduler
2313 .lock()
2314 .expect("scheduler poisoned")
2315 .repeating_tasks
2316 .push(RepeatingTask {
2317 module: current_registration(),
2318 period: period_ticks,
2319 ticks_left: period_ticks.max(1),
2320 ud,
2321 f: h,
2322 });
2323 }
2324}
2325
2326
2327
2328
2329unsafe extern "C" fn api_register_chunk_generator(
2330 ctx: *mut c_void,
2331 id: YogStr,
2332 ud: *mut c_void,
2333 h: yog_abi::YogChunkGeneratorFn,
2334) {
2335 unsafe {
2336 let mut handlers = handlers_mut(ctx);
2337 let id = id.as_str().to_owned();
2338 yog_logging::info!("registered chunk generator for dimension: {}", id);
2339 handlers
2340 .chunk_generators
2341 .insert(id, (current_registration(), ud, h));
2342 }
2343}
2344
2345unsafe extern "C" fn api_register_ui(
2346 ctx: *mut c_void,
2347 ui_id: YogStr,
2348 _layout_json: YogStr,
2349 ud: *mut c_void,
2350 h: yog_abi::YogUIEventFn,
2351) {
2352 unsafe {
2353 let mut handlers = handlers_mut(ctx);
2354 let id = ui_id.as_str().to_owned();
2355 handlers
2356 .ui_handlers
2357 .insert(id.clone(), (current_registration(), ud, h));
2358 yog_logging::info!("registered UI handler: {}", id);
2359 }
2360}
2361
2362unsafe extern "C" fn api_on_ui_render(
2363 ctx: *mut c_void,
2364 ui_id: YogStr,
2365 ud: *mut c_void,
2366 h: YogHudRenderFn,
2367) {
2368 unsafe {
2369 let mut handlers = handlers_mut(ctx);
2370 let id = ui_id.as_str().to_owned();
2371 handlers
2372 .ui_render_handlers
2373 .entry(id)
2374 .or_default()
2375 .push((current_registration(), ud, h));
2376 }
2377}
2378
2379unsafe extern "C" fn api_on_book_page(
2380 ctx: *mut c_void,
2381 drawer: YogStr,
2382 ud: *mut c_void,
2383 f: yog_abi::YogPageDrawFn,
2384) {
2385 unsafe {
2386 let mut handlers = handlers_mut(ctx);
2387 handlers
2388 .page_drawers
2389 .insert(drawer.as_str().to_owned(), (current_registration(), ud, f));
2390 }
2391}
2392
2393unsafe extern "C" fn api_on_item_tooltip(
2394 ctx: *mut c_void,
2395 item: YogStr,
2396 ud: *mut c_void,
2397 size: yog_abi::YogTooltipSizeFn,
2398 draw: yog_abi::YogTooltipDrawFn,
2399) {
2400 unsafe {
2401 let mut handlers = handlers_mut(ctx);
2402 handlers
2403 .item_tooltips
2404 .entry(item.as_str().to_owned())
2405 .or_default()
2406 .push((current_registration(), ud, size, draw));
2407 }
2408}
2409
2410unsafe extern "C" fn api_on_item_text(ctx: *mut c_void, item: YogStr, ud: *mut c_void, f: yog_abi::YogItemTextFn) {
2411 unsafe {
2412 handlers_mut(ctx).item_texts.entry(item.as_str().to_owned()).or_default().push((current_registration(), ud, f));
2413 }
2414}
2415
2416unsafe extern "C" fn copy_text(sink: *mut c_void, text: YogStr) {
2417 unsafe { (*(sink as *mut String)).push_str(text.as_str()) }
2419}
2420
2421fn item_text(item: &str, snbt: &str) -> Option<String> {
2424 let entries = handlers().item_texts.get(item).cloned().unwrap_or_default();
2425 for (module, ud, f) in entries {
2426 let Some(_call) = module.enter() else { continue };
2427 let mut out = String::new();
2428 let mut given = false;
2429 guard("item_text", || unsafe {
2430 given = f(ud, YogStr::from_str(item), YogStr::from_str(snbt), &mut out as *mut String as *mut c_void, copy_text);
2431 });
2432 if given {
2433 return Some(out);
2434 }
2435 }
2436 None
2437}
2438
2439#[unsafe(no_mangle)]
2440pub extern "system" fn Java_dev_yog_NativeBridge_nativeItemText<'l>(
2441 mut env: JNIEnv<'l>,
2442 _class: JClass<'l>,
2443 item: JString<'l>,
2444 snbt: JString<'l>,
2445) -> jstring {
2446 let item: String = env.get_string(&item).map(String::from).unwrap_or_default();
2447 let snbt: String = env.get_string(&snbt).map(String::from).unwrap_or_default();
2448 match item_text(&item, &snbt) {
2449 Some(t) => env.new_string(t).map(JString::into_raw).unwrap_or(std::ptr::null_mut()),
2450 None => std::ptr::null_mut(),
2451 }
2452}
2453
2454fn machine_event<'a>(
2458 m: &'a yog_inventory::Machine,
2459 def: &'a str,
2460 found: &'a machine::Found,
2461 dim: &'a str,
2462 pos: YogBlockPos,
2463 player: &'a str,
2464) -> YogMachineCraftEvent {
2465 YogMachineCraftEvent {
2466 machine: YogStr::from_str(def),
2467 kind: YogStr::from_str(&m.kind),
2468 recipe: YogStr::from_str(&found.recipe),
2469 result_item: YogStr::from_str(&found.result),
2470 result_count: found.count,
2471 values: YogStr::from_str(&found.values),
2472 pos,
2473 dimension: YogStr::from_str(dim),
2474 player: YogStr::from_str(player),
2475 }
2476}
2477
2478fn machine_recipe(def: &str, dim: &str, pos: YogBlockPos, items: &[String], recipe: Option<&str>) -> Option<machine::Found> {
2481 let h = handlers();
2482 let m = h.inventories.iter().find(|d| d.id == def)?.machine.as_ref()?;
2483 let has_tag = |item: &str, tag: &str| unsafe { srv_has_item_tag(std::ptr::null_mut(), YogStr::from_str(item), YogStr::from_str(tag)) };
2484 let recipes = h.recipes.iter().map(|(ns, name, r)| (format!("{ns}:{name}"), r)).filter(|(id, _)| recipe.is_none_or(|r| r == id));
2485 let srv = srv_ptr();
2486 machine::find(m, recipes, items, &has_tag).find(|found| {
2487 let ev = machine_event(m, def, found, dim, pos, "");
2488 allowed(&h.machine_craft, |ud, f| unsafe { f(ud, srv, &ev, 0) })
2489 })
2490}
2491
2492#[unsafe(no_mangle)]
2495pub extern "system" fn Java_dev_yog_NativeBridge_nativeMachineResult<'l>(
2496 mut env: JNIEnv<'l>,
2497 _class: JClass<'l>,
2498 def: JString<'l>,
2499 dimension: JString<'l>,
2500 x: jint,
2501 y: jint,
2502 z: jint,
2503 items: JString<'l>,
2504) -> jstring {
2505 let (def, d, items) = (jstr!(env, def, std::ptr::null_mut()), jstr!(env, dimension, std::ptr::null_mut()), jstr!(env, items, std::ptr::null_mut()));
2506 let items: Vec<String> = items.split('\n').map(str::to_string).collect();
2507 let mut found = None;
2508 guard("machine_result", || found = machine_recipe(&def, &d, YogBlockPos { x, y, z }, &items, None));
2509 match found {
2510 Some(f) => env.new_string(format!("{}\n{}\n{}", f.recipe, f.result, f.count)).map(JString::into_raw).unwrap_or(std::ptr::null_mut()),
2511 None => std::ptr::null_mut(),
2512 }
2513}
2514
2515#[unsafe(no_mangle)]
2518#[allow(clippy::too_many_arguments)]
2519pub extern "system" fn Java_dev_yog_NativeBridge_nativeMachineCraft<'l>(
2520 mut env: JNIEnv<'l>,
2521 _class: JClass<'l>,
2522 def: JString<'l>,
2523 recipe: JString<'l>,
2524 dimension: JString<'l>,
2525 x: jint,
2526 y: jint,
2527 z: jint,
2528 items: JString<'l>,
2529 player: JString<'l>,
2530) -> jni::sys::jboolean {
2531 let (def, recipe, d, items, player) =
2532 (jstr!(env, def, 0), jstr!(env, recipe, 0), jstr!(env, dimension, 0), jstr!(env, items, 0), jstr!(env, player, 0));
2533 let items: Vec<String> = items.split('\n').map(str::to_string).collect();
2534 let pos = YogBlockPos { x, y, z };
2535 let mut made = false;
2536 guard("machine_craft", || {
2537 let Some(found) = machine_recipe(&def, &d, pos, &items, Some(&recipe)) else { return };
2538 let h = handlers();
2539 let Some(m) = h.inventories.iter().find(|i| i.id == def).and_then(|i| i.machine.as_ref()) else { return };
2540 let ev = machine_event(m, &def, &found, &d, pos, &player);
2541 let srv = srv_ptr();
2542 each(&h.machine_craft, |ud, f| unsafe { f(ud, srv, &ev, 1) });
2543 made = true;
2544 });
2545 made as jni::sys::jboolean
2546}
2547
2548static YOG_MOD_IDS: RwLock<std::collections::BTreeSet<String>> = RwLock::new(std::collections::BTreeSet::new());
2550
2551fn mod_loaded(id: &str) -> bool {
2554 YOG_MOD_IDS.read().unwrap_or_else(|e| e.into_inner()).contains(id)
2555 || HOT_RELOADER.get().is_some_and(|r| r.all_active().iter().any(|(m, _)| m == id))
2556 || unsafe { bridge!("isModLoaded", [JArg::S(YogStr::from_str(id))]) }.unwrap_or(false)
2558}
2559
2560unsafe extern "C" fn api_is_mod_loaded(_ctx: *mut c_void, id: YogStr) -> bool {
2561 unsafe { mod_loaded(id.as_str()) }
2562}
2563
2564unsafe extern "C" fn api_java_call(_ctx: *mut c_void, expr: YogStr, args: YogStr) -> YogOwnedStr {
2565 unsafe {
2566 let args = match yog_data::text::parse_value(args.as_str()) {
2567 Ok(v) => v,
2568 Err(e) => {
2569 yog_logging::warn!("java_call {}: its arguments aren't Yog Data: {}", expr.as_str(), e.message);
2570 return YogOwnedStr::NONE;
2571 }
2572 };
2573 let bytes = yog_data::binary::encode(&args);
2574 let out: Option<Vec<u8>> = host!("dev/yog/data/YogJava", "call", [JArg::S(expr), JArg::Bytes(&bytes)]);
2575 match out.map(|b| yog_data::binary::decode(&b)) {
2576 Some(Ok(v)) => YogOwnedStr::from_string(yog_data::text::to_string(&v)),
2577 _ => YogOwnedStr::NONE,
2578 }
2579 }
2580}
2581
2582fn tsv_clean(v: &str) -> String {
2584 v.replace(['\t', '\n', '\r'], " ")
2585}
2586
2587unsafe extern "C" fn api_ui_open(_ctx: *mut c_void, ui_id: YogStr, modal: bool, pause: bool) {
2588 unsafe {
2589 if ui_id.as_str().is_empty() {
2590 return;
2591 }
2592 let _: Option<()> = bridge!("openUI", [S(ui_id), Z(modal), Z(pause)]);
2594 }
2595}
2596
2597unsafe extern "C" fn api_ui_close(_ctx: *mut c_void, ui_id: YogStr) {
2598 unsafe {
2599 let _: Option<()> = bridge!("closeUI", [S(ui_id)]);
2602 }
2603}
2604
2605unsafe extern "C" fn api_ui_consume_event(_ctx: *mut c_void) {
2606 ui_jni::consume_event();
2607}
2608
2609unsafe extern "C" fn api_translate(_ctx: *mut c_void, key: YogStr) -> YogOwnedStr {
2610 unsafe { translate(key.as_str()).map_or(YogOwnedStr::NONE, YogOwnedStr::from_string) }
2611}
2612
2613fn translate(key: &str) -> Option<String> {
2616 if key.is_empty() {
2617 return None;
2618 }
2619 unsafe { bridge!("translate", [S(YogStr::from_str(key))]) }
2620}
2621
2622unsafe extern "C" fn api_mods_list(_ctx: *mut c_void) -> YogOwnedStr {
2623 unsafe {
2624 let mut lines: Vec<String> = Vec::new();
2625 let infos = MOD_INFOS.lock().expect("mod infos lock poisoned").clone();
2626 for m in &infos {
2627 let localized = |text: &str, key: &str| {
2629 translate(key).map_or_else(|| text.to_string(), |t| tsv_clean(&t))
2630 };
2631 lines.push(format!(
2632 "yog\t{}\t{}\t{}\t{}\t{}",
2633 m[0],
2634 localized(&m[1], &m[5]),
2635 m[2],
2636 m[3],
2637 localized(&m[4], &m[6])
2638 ));
2639 }
2640 let platform: Option<String> = bridge!("listPlatformMods", []);
2642 for line in platform.iter().flat_map(|s| s.lines()) {
2643 if !line.trim().is_empty() {
2644 lines.push(format!("platform\t{line}"));
2645 }
2646 }
2647 YogOwnedStr::from_string(lines.join("\n"))
2648 }
2649}
2650
2651unsafe extern "C" fn api_register_menu_entry(ctx: *mut c_void, label: YogStr, ui_id: YogStr) {
2652 unsafe {
2653 let mut handlers = handlers_mut(ctx);
2654 let l = label.as_str().to_owned();
2655 let u = ui_id.as_str().to_owned();
2656 handlers.menu_entries.push((l, u));
2657 }
2658}
2659
2660fn vec_args(v: YogVec3) -> [JArg<'static>; 3] {
2666 [JArg::D(v.x), JArg::D(v.y), JArg::D(v.z)]
2667}
2668
2669fn pos_args(p: YogBlockPos) -> [JArg<'static>; 3] {
2670 [JArg::I(p.x), JArg::I(p.y), JArg::I(p.z)]
2671}
2672
2673unsafe extern "C" fn srv_entity_info(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
2674 unsafe { owned(bridge!("entityInfo", [JArg::S(uuid)])) }
2675}
2676
2677unsafe extern "C" fn srv_entities_in_box(
2678 _ctx: *mut c_void,
2679 dim: YogStr,
2680 min: YogVec3,
2681 max: YogVec3,
2682) -> YogOwnedStr {
2683 unsafe {
2684 let [a, b, c] = vec_args(min);
2685 let [d, e, f] = vec_args(max);
2686 owned(bridge!("entitiesInBox", [JArg::S(dim), a, b, c, d, e, f]))
2687 }
2688}
2689
2690unsafe extern "C" fn srv_raycast_block(
2691 _ctx: *mut c_void,
2692 dim: YogStr,
2693 from: YogVec3,
2694 to: YogVec3,
2695) -> YogOwnedStr {
2696 unsafe {
2697 let [a, b, c] = vec_args(from);
2698 let [d, e, f] = vec_args(to);
2699 owned(bridge!("raycastBlock", [JArg::S(dim), a, b, c, d, e, f]))
2700 }
2701}
2702
2703unsafe extern "C" fn srv_raycast_entity(
2704 _ctx: *mut c_void,
2705 dim: YogStr,
2706 from: YogVec3,
2707 to: YogVec3,
2708 exclude: YogStr,
2709) -> YogOwnedStr {
2710 unsafe {
2711 let [a, b, c] = vec_args(from);
2712 let [d, e, f] = vec_args(to);
2713 owned(bridge!(
2714 "raycastEntity",
2715 [JArg::S(dim), a, b, c, d, e, f, JArg::S(exclude)]
2716 ))
2717 }
2718}
2719
2720unsafe extern "C" fn srv_block_info(
2721 _ctx: *mut c_void,
2722 dim: YogStr,
2723 pos: YogBlockPos,
2724) -> YogOwnedStr {
2725 unsafe {
2726 let [x, y, z] = pos_args(pos);
2727 owned(bridge!("blockInfo", [JArg::S(dim), x, y, z]))
2728 }
2729}
2730
2731unsafe extern "C" fn srv_break_block(
2732 _ctx: *mut c_void,
2733 dim: YogStr,
2734 pos: YogBlockPos,
2735 drop: bool,
2736 breaker: YogStr,
2737) -> bool {
2738 unsafe {
2739 let [x, y, z] = pos_args(pos);
2740 bridge!(
2741 "breakBlock",
2742 [JArg::S(dim), x, y, z, JArg::Z(drop), JArg::S(breaker)]
2743 )
2744 .unwrap_or(false)
2745 }
2746}
2747
2748unsafe extern "C" fn srv_place_block_from_slot(
2749 _ctx: *mut c_void,
2750 player: YogStr,
2751 slot: u32,
2752 dim: YogStr,
2753 pos: YogBlockPos,
2754) -> bool {
2755 unsafe {
2756 let [x, y, z] = pos_args(pos);
2757 bridge!(
2758 "placeBlockFromSlot",
2759 [JArg::S(player), JArg::I(slot as i32), JArg::S(dim), x, y, z]
2760 )
2761 .unwrap_or(false)
2762 }
2763}
2764
2765unsafe extern "C" fn srv_bonemeal(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> bool {
2766 unsafe {
2767 let [x, y, z] = pos_args(pos);
2768 bridge!("bonemeal", [JArg::S(dim), x, y, z]).unwrap_or(false)
2769 }
2770}
2771
2772unsafe extern "C" fn srv_explode(
2773 _ctx: *mut c_void,
2774 dim: YogStr,
2775 pos: YogVec3,
2776 power: f32,
2777 fire: bool,
2778 source: YogStr,
2779) -> bool {
2780 unsafe {
2781 let [x, y, z] = vec_args(pos);
2782 bridge!(
2783 "explode",
2784 [
2785 JArg::S(dim),
2786 x,
2787 y,
2788 z,
2789 JArg::F(power),
2790 JArg::Z(fire),
2791 JArg::S(source),
2792 ]
2793 )
2794 .unwrap_or(false)
2795 }
2796}
2797
2798unsafe extern "C" fn srv_tellraw(_ctx: *mut c_void, player: YogStr, json: YogStr) -> bool {
2799 unsafe { bridge!("tellraw", [JArg::S(player), JArg::S(json)]).unwrap_or(false) }
2800}
2801
2802unsafe extern "C" fn srv_entity_damage(
2803 _ctx: *mut c_void,
2804 uuid: YogStr,
2805 amount: f32,
2806 damage_type: YogStr,
2807 attacker: YogStr,
2808) -> bool {
2809 unsafe {
2810 bridge!(
2811 "entityDamage",
2812 [
2813 JArg::S(uuid),
2814 JArg::F(amount),
2815 JArg::S(damage_type),
2816 JArg::S(attacker),
2817 ]
2818 )
2819 .unwrap_or(false)
2820 }
2821}
2822
2823unsafe extern "C" fn srv_entity_set_fire_ticks(
2824 _ctx: *mut c_void,
2825 uuid: YogStr,
2826 ticks: i32,
2827) -> bool {
2828 unsafe { bridge!("entitySetFireTicks", [JArg::S(uuid), JArg::I(ticks)]).unwrap_or(false) }
2829}
2830
2831unsafe extern "C" fn srv_player_add_xp(_ctx: *mut c_void, player: YogStr, amount: i32) -> bool {
2832 unsafe { bridge!("playerAddXp", [JArg::S(player), JArg::I(amount)]).unwrap_or(false) }
2833}
2834
2835unsafe extern "C" fn srv_player_set_may_fly(
2836 _ctx: *mut c_void,
2837 player: YogStr,
2838 may_fly: bool,
2839) -> bool {
2840 unsafe { bridge!("playerSetMayFly", [JArg::S(player), JArg::Z(may_fly)]).unwrap_or(false) }
2841}
2842
2843unsafe extern "C" fn srv_player_info(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
2844 unsafe { owned(bridge!("playerInfo", [JArg::S(player)])) }
2845}
2846
2847unsafe extern "C" fn srv_spawn_item(
2848 _ctx: *mut c_void,
2849 dim: YogStr,
2850 pos: YogVec3,
2851 vel: YogVec3,
2852 item_id: YogStr,
2853 count: u32,
2854 snbt: YogStr,
2855) -> YogOwnedStr {
2856 unsafe {
2857 let [a, b, c] = vec_args(pos);
2858 let [d, e, f] = vec_args(vel);
2859 owned(bridge!(
2860 "spawnItem",
2861 [
2862 JArg::S(dim),
2863 a,
2864 b,
2865 c,
2866 d,
2867 e,
2868 f,
2869 JArg::S(item_id),
2870 JArg::I(count as i32),
2871 JArg::S(snbt),
2872 ]
2873 ))
2874 }
2875}
2876
2877unsafe extern "C" fn srv_item_entity_stack(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
2878 unsafe { owned(bridge!("itemEntityStack", [JArg::S(uuid)])) }
2879}
2880
2881unsafe extern "C" fn srv_actionbar_json(_ctx: *mut c_void, player: YogStr, json: YogStr) -> bool {
2882 unsafe { bridge!("actionbarJson", [JArg::S(player), JArg::S(json)]).unwrap_or(false) }
2883}
2884
2885unsafe extern "C" fn srv_use_item_on_block(
2886 _ctx: *mut c_void,
2887 dim: YogStr,
2888 pos: YogBlockPos,
2889 face: u8,
2890 item_id: YogStr,
2891 player: YogStr,
2892) -> bool {
2893 unsafe {
2894 let [x, y, z] = pos_args(pos);
2895 bridge!(
2896 "useItemOnBlock",
2897 [
2898 JArg::S(dim),
2899 x,
2900 y,
2901 z,
2902 JArg::I(face as i32),
2903 JArg::S(item_id),
2904 JArg::S(player),
2905 ]
2906 )
2907 .unwrap_or(false)
2908 }
2909}
2910
2911unsafe extern "C" fn srv_pour_fluid(
2912 _ctx: *mut c_void,
2913 dim: YogStr,
2914 pos: YogBlockPos,
2915 bucket_item_id: YogStr,
2916) -> bool {
2917 unsafe {
2918 let [x, y, z] = pos_args(pos);
2919 bridge!(
2920 "pourFluid",
2921 [JArg::S(dim), x, y, z, JArg::S(bucket_item_id)]
2922 )
2923 .unwrap_or(false)
2924 }
2925}
2926
2927unsafe extern "C" fn srv_has_entity_tag(
2928 _ctx: *mut c_void,
2929 entity_type: YogStr,
2930 tag: YogStr,
2931) -> bool {
2932 unsafe { bridge!("hasEntityTag", [JArg::S(entity_type), JArg::S(tag)]).unwrap_or(false) }
2933}
2934
2935unsafe extern "C" fn srv_item_block(_ctx: *mut c_void, item_id: YogStr) -> YogOwnedStr {
2936 unsafe { owned(bridge!("itemBlock", [JArg::S(item_id)])) }
2937}
2938
2939unsafe extern "C" fn srv_place_feature(
2940 _ctx: *mut c_void,
2941 dim: YogStr,
2942 pos: YogBlockPos,
2943 feature_id: YogStr,
2944) -> bool {
2945 unsafe {
2946 let [x, y, z] = pos_args(pos);
2947 bridge!("placeFeature", [JArg::S(dim), x, y, z, JArg::S(feature_id)]).unwrap_or(false)
2948 }
2949}
2950
2951unsafe extern "C" fn srv_item_entity_set_stack(
2952 _ctx: *mut c_void,
2953 uuid: YogStr,
2954 item_id: YogStr,
2955 count: u32,
2956 snbt: YogStr,
2957) -> bool {
2958 unsafe {
2959 bridge!(
2960 "itemEntitySetStack",
2961 [
2962 JArg::S(uuid),
2963 JArg::S(item_id),
2964 JArg::I(count as i32),
2965 JArg::S(snbt),
2966 ]
2967 )
2968 .unwrap_or(false)
2969 }
2970}
2971
2972fn build_server_table() -> YogServer {
2975 YogServer {
2976 ctx: std::ptr::null_mut(),
2977 abi_version: ABI_VERSION,
2978 size: std::mem::size_of::<YogServer>() as u32,
2979 free_str: yog_free_str,
2980 broadcast: srv_broadcast,
2981 get_block: srv_get_block,
2982 set_block: srv_set_block,
2983 world_time: srv_world_time,
2984 set_time: srv_set_time,
2985 is_raining: srv_is_raining,
2986 set_weather: srv_set_weather,
2987 give_item: srv_give_item,
2988 player_teleport: srv_player_teleport,
2989 send_to_player: srv_send_to_player,
2990 send_to_server: srv_send_to_server,
2991 kick_player: srv_kick_player,
2992 set_gamemode: srv_set_gamemode,
2993 send_title: srv_send_title,
2994 send_actionbar: srv_send_actionbar,
2995 play_sound: srv_play_sound,
2996 play_sound_player: srv_play_sound_player,
2997 entity_teleport: srv_entity_teleport,
2998 entity_position: srv_entity_position,
2999 entity_rotation: srv_entity_rotation,
3000 entity_health: srv_entity_health,
3001 entity_set_health: srv_entity_set_health,
3002 entity_kill: srv_entity_kill,
3003 spawn_entity: srv_spawn_entity,
3004 entity_add_effect: srv_entity_add_effect,
3005 entity_remove_effect: srv_entity_remove_effect,
3006 entity_clear_effects: srv_entity_clear_effects,
3007 entity_velocity: srv_entity_velocity,
3008 entity_set_velocity: srv_entity_set_velocity,
3009 entity_add_velocity: srv_entity_add_velocity,
3010 has_item_tag: srv_has_item_tag,
3011 has_block_tag: srv_has_block_tag,
3012 drop_loot: srv_drop_loot,
3013 scoreboard_get: srv_scoreboard_get,
3014 scoreboard_set: srv_scoreboard_set,
3015 scoreboard_add: srv_scoreboard_add,
3016 bossbar_create: srv_bossbar_create,
3017 bossbar_remove: srv_bossbar_remove,
3018 bossbar_set_title: srv_bossbar_set_title,
3019 bossbar_set_progress: srv_bossbar_set_progress,
3020 bossbar_set_color: srv_bossbar_set_color,
3021 bossbar_add_player: srv_bossbar_add_player,
3022 bossbar_remove_player: srv_bossbar_remove_player,
3023 bossbar_set_visible: srv_bossbar_set_visible,
3024 game_dir: srv_game_dir,
3025 get_block_nbt: srv_get_block_nbt,
3026 set_block_nbt: srv_set_block_nbt,
3027 get_inventory_slot: srv_get_inventory_slot,
3028 set_inventory_slot: srv_set_inventory_slot,
3029 player_inventory: srv_player_inventory,
3030 player_set_slot: srv_player_set_slot,
3031 player_teleport_dim: srv_player_teleport_dim,
3032 entity_teleport_dim: srv_entity_teleport_dim,
3033 online_players: srv_online_players,
3034 world_entity_count: srv_world_entity_count,
3035 entity_get_nbt: srv_entity_get_nbt,
3036 entity_set_nbt: srv_entity_set_nbt,
3037 spawn_particles: srv_spawn_particles,
3038 entity_attribute_get: srv_entity_attribute_get,
3039 entity_attribute_set: srv_entity_attribute_set,
3040 get_held_item_nbt: srv_get_held_item_nbt,
3041 set_held_item_nbt: srv_set_held_item_nbt,
3042 get_offhand_item_nbt: srv_get_offhand_item_nbt,
3043 set_offhand_item_nbt: srv_set_offhand_item_nbt,
3044 get_slot_item: srv_get_slot_item,
3045 set_slot_item: srv_set_slot_item,
3046 entity_info: srv_entity_info,
3047 entities_in_box: srv_entities_in_box,
3048 raycast_block: srv_raycast_block,
3049 raycast_entity: srv_raycast_entity,
3050 block_info: srv_block_info,
3051 break_block: srv_break_block,
3052 place_block_from_slot: srv_place_block_from_slot,
3053 bonemeal: srv_bonemeal,
3054 explode: srv_explode,
3055 tellraw: srv_tellraw,
3056 entity_damage: srv_entity_damage,
3057 entity_set_fire_ticks: srv_entity_set_fire_ticks,
3058 player_add_xp: srv_player_add_xp,
3059 player_set_may_fly: srv_player_set_may_fly,
3060 player_info: srv_player_info,
3061 spawn_item: srv_spawn_item,
3062 item_entity_stack: srv_item_entity_stack,
3063 item_entity_set_stack: srv_item_entity_set_stack,
3064 actionbar_json: srv_actionbar_json,
3065 use_item_on_block: srv_use_item_on_block,
3066 pour_fluid: srv_pour_fluid,
3067 has_entity_tag: srv_has_entity_tag,
3068 item_block: srv_item_block,
3069 place_feature: srv_place_feature,
3070 set_item_cooldown: srv_set_item_cooldown,
3071 entity_vehicle: srv_entity_vehicle,
3072 entity_passengers: srv_entity_passengers,
3073 entity_mount: srv_entity_mount,
3074 entity_dismount: srv_entity_dismount,
3075 redstone_power: srv_redstone_power,
3076 entity_set_dimensions: srv_entity_set_dimensions,
3077 entity_data_set: entity_data::srv_entity_data_set,
3078 entity_data_get: entity_data::srv_entity_data_get,
3079 entity_set_fall_distance: srv_entity_set_fall_distance,
3080 redstone_signal: srv_redstone_signal,
3081 set_redstone_output: srv_set_redstone_output,
3082 get_block_state: srv_get_block_state,
3083 set_block_state: srv_set_block_state,
3084 world_is_thundering: srv_world_is_thundering,
3085 world_set_thunder: srv_world_set_thunder,
3086 fill_region: srv_fill_region,
3087 scan_region: srv_scan_region,
3088 }
3089}
3090
3091unsafe extern "C" fn srv_set_item_cooldown(_ctx: *mut c_void, player: YogStr, item_id: YogStr, ticks: i32) -> bool {
3094 unsafe { bridge!("setItemCooldown", [S(player), S(item_id), I(ticks)]).unwrap_or(false) }
3095}
3096
3097unsafe extern "C" fn srv_entity_vehicle(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
3098 unsafe { owned(bridge!("entityVehicle", [S(uuid)])) }
3099}
3100
3101unsafe extern "C" fn srv_entity_passengers(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
3102 unsafe { owned(bridge!("entityPassengers", [S(uuid)])) }
3103}
3104
3105unsafe extern "C" fn srv_entity_mount(_ctx: *mut c_void, rider: YogStr, vehicle: YogStr, force: bool) -> bool {
3106 unsafe { bridge!("entityMount", [S(rider), S(vehicle), Z(force)]).unwrap_or(false) }
3107}
3108
3109unsafe extern "C" fn srv_entity_dismount(_ctx: *mut c_void, uuid: YogStr) -> bool {
3110 unsafe { bridge!("entityDismount", [S(uuid)]).unwrap_or(false) }
3111}
3112
3113unsafe extern "C" fn srv_entity_set_dimensions(_ctx: *mut c_void, uuid: YogStr, w: f32, h: f32, eye: f32) -> bool {
3115 let found: bool = unsafe { bridge!("entitySetDimensions", [S(uuid), F(w), F(h), F(eye)]).unwrap_or(false) };
3116 if found {
3117 let value = (w >= 0.0).then(|| entity_data::encode_dims(w, h, eye));
3118 entity_data::set(unsafe { uuid.as_str() }, entity_data::DIMENSIONS, value.as_deref(), false);
3119 }
3120 found
3121}
3122
3123unsafe extern "C" fn srv_entity_set_fall_distance(_ctx: *mut c_void, uuid: YogStr, distance: f32) -> bool {
3124 unsafe { bridge!("entitySetFallDistance", [S(uuid), F(distance)]).unwrap_or(false) }
3125}
3126
3127unsafe extern "C" fn srv_redstone_signal(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> i32 {
3128 unsafe { bridge!("redstoneSignal", [S(dim), I(pos.x), I(pos.y), I(pos.z)]).unwrap_or(-1) }
3129}
3130
3131unsafe extern "C" fn srv_redstone_power(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> i32 {
3132 unsafe { bridge!("redstonePower", [S(dim), I(pos.x), I(pos.y), I(pos.z)]).unwrap_or(-1) }
3133}
3134
3135unsafe extern "C" fn srv_set_redstone_output(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos, level: i32) -> bool {
3136 unsafe { bridge!("setRedstoneOutput", [S(dim), I(pos.x), I(pos.y), I(pos.z), I(level)]).unwrap_or(false) }
3137}
3138
3139unsafe extern "C" fn srv_get_block_state(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> YogOwnedStr {
3140 unsafe { owned(bridge!("getBlockState", [S(dim), I(pos.x), I(pos.y), I(pos.z)])) }
3141}
3142
3143unsafe extern "C" fn srv_set_block_state(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos, state: YogStr) -> bool {
3144 unsafe { bridge!("setBlockState", [S(dim), I(pos.x), I(pos.y), I(pos.z), S(state)]).unwrap_or(false) }
3145}
3146
3147unsafe extern "C" fn srv_world_is_thundering(_ctx: *mut c_void, dim: YogStr) -> bool {
3148 unsafe { bridge!("worldIsThundering", [S(dim)]).unwrap_or(false) }
3149}
3150
3151unsafe extern "C" fn srv_world_set_thunder(_ctx: *mut c_void, dim: YogStr, thundering: bool, duration_ticks: i32) -> bool {
3152 unsafe { bridge!("worldSetThunder", [S(dim), Z(thundering), I(duration_ticks)]).unwrap_or(false) }
3153}
3154
3155unsafe extern "C" fn srv_fill_region(
3156 _ctx: *mut c_void,
3157 dim: YogStr,
3158 from: YogBlockPos,
3159 to: YogBlockPos,
3160 state: YogStr,
3161) -> i32 {
3162 unsafe {
3163 bridge!(
3164 "fillRegion",
3165 [S(dim), I(from.x), I(from.y), I(from.z), I(to.x), I(to.y), I(to.z), S(state)]
3166 )
3167 .unwrap_or(-1)
3168 }
3169}
3170
3171unsafe extern "C" fn srv_scan_region(_ctx: *mut c_void, dim: YogStr, from: YogBlockPos, to: YogBlockPos) -> YogOwnedStr {
3172 unsafe { owned(bridge!("scanRegion", [S(dim), I(from.x), I(from.y), I(from.z), I(to.x), I(to.y), I(to.z)])) }
3173}
3174
3175unsafe extern "C" fn interop_export_impl(
3178 _ctx: *mut c_void,
3179 mod_id: YogStr,
3180 symbol: YogStr,
3181 ptr: *const c_void,
3182) {
3183 unsafe {
3184 let mod_id = mod_id.as_str();
3185 let sym = symbol.as_str();
3186 let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
3187 registry
3188 .lock()
3189 .expect("interop symbols lock poisoned")
3190 .entry(mod_id.to_string())
3191 .or_default()
3192 .insert(sym.to_string(), ptr as usize);
3193 }
3194}
3195
3196unsafe extern "C" fn interop_import_impl(
3197 _ctx: *mut c_void,
3198 mod_id: YogStr,
3199 symbol: YogStr,
3200) -> *const c_void {
3201 unsafe {
3202 let mod_id = mod_id.as_str();
3203 let sym = symbol.as_str();
3204 let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
3205 registry
3206 .lock()
3207 .expect("interop symbols lock poisoned")
3208 .get(mod_id)
3209 .and_then(|m: &HashMap<String, usize>| m.get(sym))
3210 .map(|&ptr| ptr as *const c_void)
3211 .unwrap_or(std::ptr::null())
3212 }
3213}
3214
3215fn build_api_table(ctx: *const RwLock<RuntimeHandlers>, server: *const YogServer) -> YogApi {
3216 YogApi {
3217 abi_version: ABI_VERSION,
3218 size: std::mem::size_of::<YogApi>() as u32,
3219 ctx: ctx as *mut c_void,
3220 server,
3221 on_block_break: api_on_block_break,
3222 on_chat: api_on_chat,
3223 on_player_join: api_on_player_join,
3224 on_player_leave: api_on_player_leave,
3225 on_use_item: api_on_use_item,
3226 on_use_block: api_on_use_block,
3227 on_attack_entity: api_on_attack_entity,
3228 on_entity_damage: api_on_entity_damage,
3229 on_entity_death: api_on_entity_death,
3230 on_entity_spawn: api_on_entity_spawn,
3231 on_player_place_block: api_on_player_place_block,
3232 on_player_death: api_on_player_death,
3233 on_player_respawn: api_on_player_respawn,
3234 on_advancement: api_on_advancement,
3235 on_entity_interact: api_on_entity_interact,
3236 on_item_craft: api_on_item_craft,
3237 on_explosion: api_on_explosion,
3238 on_item_pickup: api_on_item_pickup,
3239 on_item_insert: api_on_item_insert,
3240 on_player_move: api_on_player_move,
3241 on_container_open: api_on_container_open,
3242 on_container_close: api_on_container_close,
3243 on_projectile_hit: api_on_projectile_hit,
3244 on_server_tick: api_on_server_tick,
3245 on_server_started: api_on_server_started,
3246 on_server_stopping: api_on_server_stopping,
3247 on_packet: api_on_packet,
3248 on_client_packet: api_on_client_packet,
3249 register_command: api_register_command,
3250 register_typed_command: api_register_typed_command,
3251 register_content: api_register_content,
3252 schedule_once: api_schedule_once,
3253 schedule_repeating: api_schedule_repeating,
3254 on_client_tick: api_on_client_tick,
3255 on_hud_render: api_on_hud_render,
3256 on_key_press: api_on_key_press,
3257 on_screen_open: api_on_screen_open,
3258 on_screen_close: api_on_screen_close,
3259 on_world_render: api_on_world_render,
3260 register_ui: api_register_ui,
3261 on_ui_render: api_on_ui_render,
3262 register_menu_entry: api_register_menu_entry,
3263 mods_list: api_mods_list,
3264 free_str: yog_free_str,
3265 ui_open: api_ui_open,
3266 interop_export: interop_export_impl,
3267 interop_import: interop_import_impl,
3268 register_chunk_generator: api_register_chunk_generator,
3269 ui_close: api_ui_close,
3270 ui_consume_event: api_ui_consume_event,
3271 translate: api_translate,
3272 ui_open_flags: client_api::api_ui_open_flags,
3273 client_play_sound: client_api::api_client_play_sound,
3274 client_stop_sound: client_api::api_client_stop_sound,
3275 client_held_item: client_api::api_client_held_item,
3276 client_set_block_tint: client_api::api_client_set_block_tint,
3277 client_key_down: client_api::api_client_key_down,
3278 on_book_page: api_on_book_page,
3279 on_item_tooltip: api_on_item_tooltip,
3280 is_mod_loaded: api_is_mod_loaded,
3281 java_call: api_java_call,
3282 on_item_text: api_on_item_text,
3283 client_body_track: body::api_client_body_track,
3284 client_body_track_type: body::api_client_body_track_type,
3285 client_set_dimensions: body::api_client_set_dimensions,
3286 client_entity_data: entity_data::api_client_entity_data,
3287 on_body: body::api_on_body,
3288 on_camera: body::api_on_camera,
3289 on_travel: body::api_on_travel,
3290 on_machine_craft: api_on_machine_craft,
3291 }
3292}
3293
3294fn platform_tag() -> String {
3297 format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
3298}
3299
3300type AbiVersionFn = unsafe extern "C" fn() -> u32;
3301type RegisterFn = unsafe extern "C" fn(*const YogApi, *const std::os::raw::c_char);
3303
3304fn load_mods(dir: &Path, api: &YogApi) {
3305 let entries = match std::fs::read_dir(dir) {
3306 Ok(e) => e,
3307 Err(_) => {
3308 yog_logging::info!("no mods directory at {} — none loaded", dir.display());
3309 return;
3310 }
3311 };
3312
3313 struct ModMeta {
3315 id: String,
3316 deps: Vec<String>,
3317 yog_path: PathBuf,
3318 }
3319 let mut metas: Vec<ModMeta> = Vec::new();
3320 for entry in entries.flatten() {
3321 let path = entry.path();
3322 if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3323 continue;
3324 }
3325 let info = read_mod_info(&path);
3326 let deps = read_mod_deps(&path);
3327 metas.push(ModMeta {
3328 id: info[0].clone(),
3329 deps,
3330 yog_path: path,
3331 });
3332 }
3333
3334 let ids: Vec<String> = metas.iter().map(|m| m.id.clone()).collect();
3336 YOG_MOD_IDS.write().unwrap_or_else(|e| e.into_inner()).extend(ids.iter().cloned());
3339 let deps_list: Vec<Vec<String>> = metas.iter().map(|m| m.deps.clone()).collect();
3340 let sorted = match topo_sort_mods(&ids, &deps_list) {
3341 Some(v) => v,
3342 None => {
3343 yog_logging::error!("circular dependency detected — loading in file order");
3344 (0..metas.len()).collect()
3345 }
3346 };
3347
3348 let mut loaded: Vec<LoadedModule> = Vec::new();
3349 for idx in sorted {
3350 let meta = &metas[idx];
3351 let lib_path = match extract_yog(&meta.yog_path) {
3352 Some(p) => p,
3353 None => {
3354 yog_logging::error!(
3355 "no native for {} in {}",
3356 platform_tag(),
3357 meta.yog_path.display()
3358 );
3359 continue;
3360 }
3361 };
3362 if let Some(module) = load_mod_lib(&lib_path, api, &meta.id) {
3363 loaded.push(module);
3364 MOD_INFOS
3365 .lock()
3366 .expect("mod infos lock poisoned")
3367 .push(read_mod_info(&meta.yog_path));
3368 }
3369 }
3370 yog_logging::info!("loaded {} mod(s) from {}", loaded.len(), dir.display());
3371
3372 resolve_all_imports(api, &loaded);
3375}
3376
3377fn resolve_all_imports(api: &YogApi, loaded: &[LoadedModule]) {
3380 for module in loaded {
3381 module.with_library(|lib| unsafe {
3382 type ResolveFn = unsafe extern "C" fn(*const YogApi);
3383 if let Ok(resolve) = lib.get::<ResolveFn>(b"__yog_resolve_imports_final") {
3384 resolve(api as *const YogApi);
3385 }
3386 });
3387 }
3388}
3389
3390fn topo_sort_mods(ids: &[String], deps_list: &[Vec<String>]) -> Option<Vec<usize>> {
3392 let n = ids.len();
3393 let name_to_idx: HashMap<&str, usize> = ids
3394 .iter()
3395 .enumerate()
3396 .map(|(i, id)| (id.as_str(), i))
3397 .collect();
3398 let mut in_degree = vec![0u32; n];
3399 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
3400 for (i, deps) in deps_list.iter().enumerate() {
3401 for dep in deps {
3402 if let Some(&j) = name_to_idx.get(dep.as_str()) {
3403 adj[j].push(i);
3404 in_degree[i] += 1;
3405 }
3406 }
3407 }
3408 let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
3409 let mut order = Vec::with_capacity(n);
3410 while let Some(u) = queue.pop() {
3411 order.push(u);
3412 for &v in &adj[u] {
3413 in_degree[v] -= 1;
3414 if in_degree[v] == 0 {
3415 queue.push(v);
3416 }
3417 }
3418 }
3419 if order.len() == n { Some(order) } else { None }
3420}
3421
3422fn load_mod_lib(path: &Path, api: &YogApi, mod_id: &str) -> Option<LoadedModule> {
3430 let module = match unsafe { hot_reloader().load_initial(mod_id, path) } {
3431 Ok(m) => m,
3432 Err(e) => {
3433 yog_logging::error!("failed to load {}: {}", path.display(), e);
3434 return None;
3435 }
3436 };
3437
3438 let abi = module.with_library(|lib| unsafe {
3439 let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
3440 Some(abi())
3441 });
3442 match abi.flatten() {
3443 Some(v) if v == ABI_VERSION => {}
3444 Some(v) => {
3445 yog_logging::error!(
3446 "{}: ABI {} incompatible with runtime ABI {}",
3447 path.display(),
3448 v,
3449 ABI_VERSION
3450 );
3451 module.retire(|| {});
3452 return None;
3453 }
3454 None => {
3455 yog_logging::error!("{} is not a Yog mod (no yog_abi_version)", path.display());
3456 module.retire(|| {});
3457 return None;
3458 }
3459 }
3460
3461 if let Err(e) = invoke_mod_register(&module, api, mod_id) {
3462 yog_logging::error!("{}: {}", path.display(), e);
3463 module.retire(|| {});
3464 return None;
3465 }
3466
3467 Some(module)
3468}
3469
3470fn invoke_mod_register(module: &LoadedModule, api: &YogApi, mod_id: &str) -> Result<(), String> {
3474 module
3475 .with_library(|lib| unsafe {
3476 let register: Symbol<RegisterFn> = lib
3477 .get(b"yog_mod_register")
3478 .map_err(|_| "missing yog_mod_register".to_string())?;
3479 let mod_id_c = std::ffi::CString::new(mod_id).unwrap();
3480 *CURRENT_REGISTRATION.lock().unwrap() = Some(module.clone());
3481 register(api as *const YogApi, mod_id_c.as_ptr());
3482 *CURRENT_REGISTRATION.lock().unwrap() = None;
3483 Ok(())
3484 })
3485 .unwrap_or_else(|| Err("module was unloaded before registration could run".to_string()))
3486}
3487
3488fn read_mod_info(path: &Path) -> [String; 7] {
3493 let stem = path
3494 .file_stem()
3495 .map(|s| s.to_string_lossy().into_owned())
3496 .unwrap_or_default();
3497 let mut info = [
3498 stem.clone(),
3499 stem,
3500 String::new(),
3501 String::new(),
3502 String::new(),
3503 String::new(),
3504 String::new(),
3505 ];
3506 if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3507 return info;
3508 }
3509 let Some(text) = (|| {
3510 let file = std::fs::File::open(path).ok()?;
3511 let mut archive = zip::ZipArchive::new(file).ok()?;
3512 let mut entry = archive.by_name("yog.toml").ok()?;
3513 let mut text = String::new();
3514 std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
3515 Some(text)
3516 })() else {
3517 return info;
3518 };
3519 let mut in_mod = true;
3524 for line in text.lines() {
3525 let line = line.trim();
3526 if line.starts_with('[') {
3527 in_mod = line == "[mod]";
3528 continue;
3529 }
3530 if !in_mod {
3531 continue;
3532 }
3533 let Some((key, value)) = line.split_once('=') else {
3534 continue;
3535 };
3536 let key = key.trim();
3537 let value = value.trim();
3538 let unquote = |v: &str| v.trim().trim_matches('"').to_string();
3539 match key {
3540 "id" => info[0] = tsv_clean(&unquote(value)),
3541 "name" => info[1] = tsv_clean(&unquote(value)),
3542 "version" => info[2] = tsv_clean(&unquote(value)),
3543 "description" => info[4] = tsv_clean(&unquote(value)),
3544 "name_key" => info[5] = unquote(value),
3545 "description_key" => info[6] = unquote(value),
3546 "authors" => {
3547 let list = value.trim_start_matches('[').trim_end_matches(']');
3548 let authors: Vec<String> = list
3549 .split(',')
3550 .map(unquote)
3551 .filter(|a| !a.is_empty())
3552 .collect();
3553 info[3] = tsv_clean(&authors.join(", "));
3554 }
3555 _ => {}
3556 }
3557 }
3558 info
3559}
3560
3561fn read_mod_deps(path: &Path) -> Vec<String> {
3563 if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3564 return Vec::new();
3565 }
3566 let text = (|| {
3567 let file = std::fs::File::open(path).ok()?;
3568 let mut archive = zip::ZipArchive::new(file).ok()?;
3569 let mut entry = archive.by_name("yog.toml").ok()?;
3570 let mut text = String::new();
3571 std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
3572 Some(text)
3573 })();
3574 let Some(text) = text else { return Vec::new() };
3575 let mut deps = Vec::new();
3576 let mut in_deps = false;
3577 for line in text.lines() {
3578 let line = line.trim();
3579 if line.starts_with('[') {
3580 in_deps = line == "[dependencies]";
3581 continue;
3582 }
3583 if !in_deps {
3584 continue;
3585 }
3586 if let Some((key, _)) = line.split_once('=') {
3588 deps.push(key.trim().to_string());
3589 }
3590 }
3591 deps
3592}
3593
3594fn extract_yog(path: &Path) -> Option<PathBuf> {
3595 let file = std::fs::File::open(path).ok()?;
3596 let mut archive = zip::ZipArchive::new(file).ok()?;
3597 let prefix = format!("natives/{}/", platform_tag());
3598 let mut entry_name = None;
3599 for i in 0..archive.len() {
3600 let f = archive.by_index(i).ok()?;
3601 let name = f.name().ok()?;
3602 if name.starts_with(&prefix) && !name.ends_with('/') {
3603 entry_name = Some(name.to_string());
3604 break;
3605 }
3606 }
3607 let entry_name = entry_name?;
3608 let ext = Path::new(&entry_name)
3609 .extension()
3610 .and_then(|e| e.to_str())
3611 .unwrap_or("bin");
3612 let stem = path.file_stem()?.to_string_lossy().into_owned();
3613 static EXTRACTIONS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
3617 let n = EXTRACTIONS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3618 let out =
3619 std::env::temp_dir().join(format!("yog-{}-{}-{}.{}", stem, std::process::id(), n, ext));
3620 let mut entry = archive.by_name(&entry_name).ok()?;
3621 let mut out_file = std::fs::File::create(&out).ok()?;
3622 std::io::copy(&mut entry, &mut out_file).ok()?;
3623 Some(out)
3624}
3625
3626fn srv_ptr() -> *const YogServer {
3629 SERVER.get().expect("yog: SERVER not initialised") as *const YogServer
3630}
3631
3632#[unsafe(no_mangle)]
3635pub extern "system" fn Java_dev_yog_NativeBridge_nativeInit<'l>(
3636 mut env: JNIEnv<'l>,
3637 _class: JClass<'l>,
3638 mods_dir: JString<'l>,
3639) {
3640 if let Ok(vm) = env.get_java_vm() {
3641 let _ = JAVA_VM.set(vm);
3642 }
3643
3644 let dir = env
3645 .get_string(&mods_dir)
3646 .map(String::from)
3647 .unwrap_or_default();
3648
3649 let _ = SERVER.set(build_server_table());
3651 let server_ptr = SERVER.get().unwrap() as *const YogServer;
3652
3653 let handlers_lock = HANDLERS.get_or_init(|| RwLock::new(RuntimeHandlers::default()));
3659 let handlers_ptr = handlers_lock as *const RwLock<RuntimeHandlers>;
3660
3661 let api: &'static YogApi = API_TABLE.get_or_init(|| build_api_table(handlers_ptr, server_ptr));
3662
3663 let _ = HOT_RELOADER.get_or_init(|| HotReloader::new(RuntimeModuleRegistry));
3664
3665 guard("mod loading", || {
3666 load_mods(Path::new(&dir), api);
3667 });
3668
3669 control_socket::start_if_requested();
3670
3671 yog_logging::info!("runtime initialised — the gate is open.");
3672}
3673
3674#[unsafe(no_mangle)]
3675pub extern "system" fn Java_dev_yog_NativeBridge_nativeHotReload<'l>(
3676 mut env: JNIEnv<'l>,
3677 _class: JClass<'l>,
3678 mod_id: JString<'l>,
3679 new_yog_path: JString<'l>,
3680) -> jni::sys::jboolean {
3681 let Ok(mod_id) = env.get_string(&mod_id).map(String::from) else {
3682 return 0;
3683 };
3684 let Ok(yog_path) = env.get_string(&new_yog_path).map(String::from) else {
3685 return 0;
3686 };
3687 let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3688 trigger_hot_reload(&mod_id, Path::new(&yog_path))
3689 }))
3690 .unwrap_or(false);
3691 ok as jni::sys::jboolean
3692}
3693
3694fn trigger_hot_reload(mod_id: &str, new_yog_path: &Path) -> bool {
3704 let Some(native_path) = extract_yog(new_yog_path) else {
3705 yog_logging::error!(
3706 "hot-reload {}: no native for {} in {}",
3707 mod_id,
3708 platform_tag(),
3709 new_yog_path.display()
3710 );
3711 return false;
3712 };
3713
3714 let api = API_TABLE.get().expect("yog: nativeInit not called yet");
3715
3716 let module = match unsafe { hot_reloader().reload(mod_id, &native_path) } {
3717 Ok(m) => m,
3718 Err(e) => {
3719 yog_logging::error!(
3720 "hot-reload {}: failed to load {}: {}",
3721 mod_id,
3722 native_path.display(),
3723 e
3724 );
3725 return false;
3726 }
3727 };
3728
3729 let abi = module.with_library(|lib| unsafe {
3730 let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
3731 Some(abi())
3732 });
3733 match abi.flatten() {
3734 Some(v) if v == ABI_VERSION => {}
3735 other => {
3736 yog_logging::error!(
3737 "hot-reload {}: ABI {:?} incompatible with runtime ABI {} — mod has no active handlers until a compatible reload succeeds",
3738 mod_id,
3739 other,
3740 ABI_VERSION
3741 );
3742 return false;
3743 }
3744 }
3745
3746 if let Err(e) = invoke_mod_register(&module, api, mod_id) {
3747 yog_logging::error!("hot-reload {}: {}", mod_id, e);
3748 return false;
3749 }
3750
3751 yog_logging::info!("hot-reloaded mod {}", mod_id);
3752 true
3753}
3754
3755#[unsafe(no_mangle)]
3756pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreak<'l>(
3757 mut env: JNIEnv<'l>,
3758 _class: JClass<'l>,
3759 player: JString<'l>,
3760 block: JString<'l>,
3761 x: jint,
3762 y: jint,
3763 z: jint,
3764 dimension: JString<'l>,
3765) {
3766 let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
3767 let ev = yog_abi::YogBlockBreakEvent {
3768 player: YogStr::from_str(&p),
3769 block: YogStr::from_str(&b),
3770 pos: YogBlockPos { x, y, z },
3771 dimension: YogStr::from_str(&d),
3772 };
3773 let srv = srv_ptr();
3774 guard("on_block_break", || {
3775 each(&handlers().block_break, |ud, f| unsafe {
3776 f(ud, srv, &ev, 1)
3777 });
3778 });
3779}
3780
3781#[unsafe(no_mangle)]
3782pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChat<'l>(
3783 mut env: JNIEnv<'l>,
3784 _class: JClass<'l>,
3785 player: JString<'l>,
3786 message: JString<'l>,
3787 dimension: JString<'l>,
3788) {
3789 let (p, m, d) = (
3790 jstr!(env, player),
3791 jstr!(env, message),
3792 jstr!(env, dimension),
3793 );
3794 let ev = yog_abi::YogChatEvent {
3795 player: YogStr::from_str(&p),
3796 message: YogStr::from_str(&m),
3797 dimension: YogStr::from_str(&d),
3798 };
3799 let srv = srv_ptr();
3800 guard("on_chat", || {
3801 each(&handlers().chat, |ud, f| unsafe { f(ud, srv, &ev, 1) });
3802 });
3803}
3804
3805#[unsafe(no_mangle)]
3806pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerJoin<'l>(
3807 mut env: JNIEnv<'l>,
3808 _class: JClass<'l>,
3809 player: JString<'l>,
3810 uuid: JString<'l>,
3811 dimension: JString<'l>,
3812) {
3813 let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
3814 let ev = yog_abi::YogPlayerEvent {
3815 player: YogStr::from_str(&p),
3816 uuid: YogStr::from_str(&u),
3817 dimension: YogStr::from_str(&d),
3818 };
3819 let srv = srv_ptr();
3820 guard("on_player_join", || {
3821 each(&handlers().player_join, |ud, f| unsafe {
3822 f(ud, srv, &ev, 1)
3823 });
3824 });
3825 let h = handlers();
3829 if !h.startup_grants.is_empty() {
3830 h.pending_grants
3831 .lock()
3832 .expect("pending_grants poisoned")
3833 .push((p, u, 0));
3834 }
3835}
3836
3837fn grant_tag(id: &str) -> String {
3842 format!("yog.grant.{id}")
3843}
3844
3845fn process_pending_grants() {
3848 let h = handlers();
3849 let mut pending = match h.pending_grants.try_lock() {
3850 Ok(g) => g,
3851 Err(_) => return,
3852 };
3853 if pending.is_empty() {
3854 return;
3855 }
3856 const FIRST_TRY_TICK: u32 = 2;
3858 const MAX_TICKS: u32 = 600;
3860
3861 let mut still_pending = Vec::new();
3862 for (p, u, ticks) in pending.drain(..) {
3863 if ticks < FIRST_TRY_TICK {
3864 still_pending.push((p, u, ticks + 1));
3865 continue;
3866 }
3867 let player = YogStr::from_str(&p);
3868 let mut all_ok = true;
3869 for sg in &h.startup_grants {
3870 let tag = grant_tag(&sg.id);
3871 let tag = YogStr::from_str(&tag);
3872 if unsafe { bridge!("playerHasTag", [S(player), S(tag)]) }.unwrap_or(false) {
3873 continue;
3874 }
3875 let book = sg.book.as_ref().map(|_| "minecraft:written_book");
3876 let mut ok = true;
3877 for item_id in sg.items.iter().map(String::as_str).chain(book) {
3878 ok &= unsafe { srv_give_item(std::ptr::null_mut(), player, YogStr::from_str(item_id), 1) };
3879 }
3880 if ok && unsafe { bridge!("addPlayerTag", [S(player), S(tag)]) }.unwrap_or(false) {
3881 yog_logging::info!("startup grant {} given to {}", sg.id, p);
3882 } else {
3883 all_ok = false;
3884 }
3885 }
3886 if !all_ok && ticks < MAX_TICKS {
3887 still_pending.push((p, u, ticks + 1));
3888 }
3889 }
3890 *pending = still_pending;
3891}
3892
3893#[unsafe(no_mangle)]
3894pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerLeave<'l>(
3895 mut env: JNIEnv<'l>,
3896 _class: JClass<'l>,
3897 player: JString<'l>,
3898 uuid: JString<'l>,
3899 dimension: JString<'l>,
3900) {
3901 let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
3902 let ev = yog_abi::YogPlayerEvent {
3903 player: YogStr::from_str(&p),
3904 uuid: YogStr::from_str(&u),
3905 dimension: YogStr::from_str(&d),
3906 };
3907 let srv = srv_ptr();
3908 guard("on_player_leave", || {
3909 each(&handlers().player_leave, |ud, f| unsafe {
3910 f(ud, srv, &ev, 1)
3911 });
3912 });
3913}
3914
3915#[unsafe(no_mangle)]
3916pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItem<'l>(
3917 mut env: JNIEnv<'l>,
3918 _class: JClass<'l>,
3919 player: JString<'l>,
3920 item: JString<'l>,
3921 sneaking: jni::sys::jboolean,
3922 dimension: JString<'l>,
3923) {
3924 let (p, i, d) = (jstr!(env, player), jstr!(env, item), jstr!(env, dimension));
3925 let ev = yog_abi::YogUseItemEvent {
3926 player: YogStr::from_str(&p),
3927 item: YogStr::from_str(&i),
3928 sneaking: sneaking != 0,
3929 dimension: YogStr::from_str(&d),
3930 };
3931 let srv = srv_ptr();
3932 guard("on_use_item", || {
3933 each(&handlers().use_item, |ud, f| unsafe { f(ud, srv, &ev, 1) });
3934 });
3935}
3936
3937#[unsafe(no_mangle)]
3938pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItemPre<'l>(
3939 mut env: JNIEnv<'l>,
3940 _class: JClass<'l>,
3941 player: JString<'l>,
3942 item: JString<'l>,
3943 sneaking: jni::sys::jboolean,
3944 dimension: JString<'l>,
3945) -> jni::sys::jboolean {
3946 let h = handlers();
3947 if h.use_item.is_empty() {
3948 return 1;
3949 }
3950 let p = jstr!(env, player, 1);
3951 let i = jstr!(env, item, 1);
3952 let d = jstr!(env, dimension, 1);
3953 let ev = yog_abi::YogUseItemEvent {
3954 player: YogStr::from_str(&p),
3955 item: YogStr::from_str(&i),
3956 sneaking: sneaking != 0,
3957 dimension: YogStr::from_str(&d),
3958 };
3959 let srv = srv_ptr();
3960 allowed(&h.use_item, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
3961}
3962
3963#[unsafe(no_mangle)]
3964pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlock<'l>(
3965 mut env: JNIEnv<'l>,
3966 _class: JClass<'l>,
3967 player: JString<'l>,
3968 block: JString<'l>,
3969 x: jint,
3970 y: jint,
3971 z: jint,
3972 dimension: JString<'l>,
3973 sneaking: jni::sys::jboolean,
3974) {
3975 let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
3976 let ev = yog_abi::YogUseBlockEvent {
3977 player: YogStr::from_str(&p),
3978 block: YogStr::from_str(&b),
3979 pos: YogBlockPos { x, y, z },
3980 dimension: YogStr::from_str(&d),
3981 sneaking: sneaking != 0,
3982 };
3983 let srv = srv_ptr();
3984 guard("on_use_block", || {
3985 each(&handlers().use_block, |ud, f| unsafe { f(ud, srv, &ev, 1) });
3986 });
3987}
3988
3989#[unsafe(no_mangle)]
3990pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlockPre<'l>(
3991 mut env: JNIEnv<'l>,
3992 _class: JClass<'l>,
3993 player: JString<'l>,
3994 block: JString<'l>,
3995 x: jint,
3996 y: jint,
3997 z: jint,
3998 dimension: JString<'l>,
3999 sneaking: jni::sys::jboolean,
4000) -> jni::sys::jboolean {
4001 let h = handlers();
4002 if h.use_block.is_empty() {
4003 return 1;
4004 }
4005 let p = jstr!(env, player, 1);
4006 let b = jstr!(env, block, 1);
4007 let d = jstr!(env, dimension, 1);
4008 let ev = yog_abi::YogUseBlockEvent {
4009 player: YogStr::from_str(&p),
4010 block: YogStr::from_str(&b),
4011 pos: YogBlockPos { x, y, z },
4012 dimension: YogStr::from_str(&d),
4013 sneaking: sneaking != 0,
4014 };
4015 let srv = srv_ptr();
4016 allowed(&h.use_block, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4017}
4018
4019#[unsafe(no_mangle)]
4020pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAttackEntity<'l>(
4021 mut env: JNIEnv<'l>,
4022 _class: JClass<'l>,
4023 player: JString<'l>,
4024 target_type: JString<'l>,
4025 target_uuid: JString<'l>,
4026 dimension: JString<'l>,
4027) {
4028 let (p, tt, tu, d) = (
4029 jstr!(env, player),
4030 jstr!(env, target_type),
4031 jstr!(env, target_uuid),
4032 jstr!(env, dimension),
4033 );
4034 let ev = yog_abi::YogAttackEntityEvent {
4035 player: YogStr::from_str(&p),
4036 target_type: YogStr::from_str(&tt),
4037 target_uuid: YogStr::from_str(&tu),
4038 dimension: YogStr::from_str(&d),
4039 };
4040 let srv = srv_ptr();
4041 guard("on_attack_entity", || {
4042 each(&handlers().attack_entity, |ud, f| unsafe {
4043 f(ud, srv, &ev, 1)
4044 });
4045 });
4046}
4047
4048#[unsafe(no_mangle)]
4049pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamage<'l>(
4050 mut env: JNIEnv<'l>,
4051 _class: JClass<'l>,
4052 entity_type: JString<'l>,
4053 uuid: JString<'l>,
4054 amount: jfloat,
4055 source: JString<'l>,
4056 dimension: JString<'l>,
4057) {
4058 let (et, u, s, d) = (
4059 jstr!(env, entity_type),
4060 jstr!(env, uuid),
4061 jstr!(env, source),
4062 jstr!(env, dimension),
4063 );
4064 let ev = yog_abi::YogEntityDamageEvent {
4065 entity_type: YogStr::from_str(&et),
4066 uuid: YogStr::from_str(&u),
4067 amount,
4068 source: YogStr::from_str(&s),
4069 dimension: YogStr::from_str(&d),
4070 };
4071 let srv = srv_ptr();
4072 guard("on_entity_damage", || {
4073 each(&handlers().entity_damage, |ud, f| unsafe {
4074 f(ud, srv, &ev, 1)
4075 });
4076 });
4077}
4078
4079#[unsafe(no_mangle)]
4080pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDeath<'l>(
4081 mut env: JNIEnv<'l>,
4082 _class: JClass<'l>,
4083 entity_type: JString<'l>,
4084 uuid: JString<'l>,
4085 source: JString<'l>,
4086 dimension: JString<'l>,
4087) {
4088 let (et, u, s, d) = (
4089 jstr!(env, entity_type),
4090 jstr!(env, uuid),
4091 jstr!(env, source),
4092 jstr!(env, dimension),
4093 );
4094 let ev = yog_abi::YogEntityDeathEvent {
4095 entity_type: YogStr::from_str(&et),
4096 uuid: YogStr::from_str(&u),
4097 source: YogStr::from_str(&s),
4098 dimension: YogStr::from_str(&d),
4099 };
4100 let srv = srv_ptr();
4101 guard("on_entity_death", || {
4102 each(&handlers().entity_death, |ud, f| unsafe {
4103 f(ud, srv, &ev, 1)
4104 });
4105 });
4106}
4107
4108#[unsafe(no_mangle)]
4109pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnTick<'l>(
4110 _env: JNIEnv<'l>,
4111 _class: JClass<'l>,
4112) {
4113 let h = handlers();
4114 let srv = srv_ptr();
4115 guard("on_tick", || {
4116 each(&h.server_tick, |ud, f| unsafe { f(ud, srv) });
4117 });
4118
4119 guard("startup_grants", process_pending_grants);
4120
4121 guard("entity data", entity_data::flush);
4123
4124 let due = h.scheduler.lock().expect("scheduler poisoned").due();
4126 for (module, ud, f) in due {
4127 if module.is_retiring() {
4128 continue;
4129 }
4130 let Some(_call) = module.enter() else {
4131 continue;
4132 };
4133 guard("scheduled task", || unsafe { f(ud, srv) });
4134 }
4135}
4136
4137#[unsafe(no_mangle)]
4138pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStarted<'l>(
4139 _env: JNIEnv<'l>,
4140 _class: JClass<'l>,
4141 _world_dir: JString<'l>,
4142) {
4143 handlers().pending_grants.lock().expect("pending_grants poisoned").clear();
4145 let srv = srv_ptr();
4146 guard("on_server_started", || {
4147 each(&handlers().server_started, |ud, f| unsafe { f(ud, srv) });
4148 });
4149}
4150
4151#[unsafe(no_mangle)]
4152pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStopping<'l>(
4153 _env: JNIEnv<'l>,
4154 _class: JClass<'l>,
4155) {
4156 let srv = srv_ptr();
4157 guard("on_server_stopping", || {
4158 each(&handlers().server_stopping, |ud, f| unsafe { f(ud, srv) });
4159 });
4160 entity_data::server_stopped();
4161}
4162
4163#[unsafe(no_mangle)]
4164pub extern "system" fn Java_dev_yog_NativeBridge_nativeCommandNames<'l>(
4165 env: JNIEnv<'l>,
4166 _class: JClass<'l>,
4167) -> jstring {
4168 let names = handlers()
4169 .commands
4170 .keys()
4171 .cloned()
4172 .collect::<Vec<_>>()
4173 .join("\n");
4174 env.new_string(names)
4175 .map(jni::objects::JString::into_raw)
4176 .unwrap_or(std::ptr::null_mut())
4177}
4178
4179#[unsafe(no_mangle)]
4180pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreakPre<'l>(
4181 mut env: JNIEnv<'l>,
4182 _class: JClass<'l>,
4183 player: JString<'l>,
4184 block: JString<'l>,
4185 x: jint,
4186 y: jint,
4187 z: jint,
4188 dimension: JString<'l>,
4189) -> jni::sys::jboolean {
4190 let h = handlers();
4191 if h.block_break.is_empty() {
4192 return 1;
4193 }
4194 let p = jstr!(env, player, 1);
4195 let b = jstr!(env, block, 1);
4196 let d = jstr!(env, dimension, 1);
4197 let ev = yog_abi::YogBlockBreakEvent {
4198 player: YogStr::from_str(&p),
4199 block: YogStr::from_str(&b),
4200 pos: YogBlockPos { x, y, z },
4201 dimension: YogStr::from_str(&d),
4202 };
4203 let srv = srv_ptr();
4204 allowed(&h.block_break, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4205}
4206
4207#[unsafe(no_mangle)]
4208pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChatPre<'l>(
4209 mut env: JNIEnv<'l>,
4210 _class: JClass<'l>,
4211 player: JString<'l>,
4212 message: JString<'l>,
4213 dimension: JString<'l>,
4214) -> jni::sys::jboolean {
4215 let h = handlers();
4216 if h.chat.is_empty() {
4217 return 1;
4218 }
4219 let p = jstr!(env, player, 1);
4220 let m = jstr!(env, message, 1);
4221 let d = jstr!(env, dimension, 1);
4222 let ev = yog_abi::YogChatEvent {
4223 player: YogStr::from_str(&p),
4224 message: YogStr::from_str(&m),
4225 dimension: YogStr::from_str(&d),
4226 };
4227 let srv = srv_ptr();
4228 allowed(&h.chat, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4229}
4230
4231unsafe extern "C" fn chunk_writer_set_block(
4236 _ctx: *mut c_void,
4237 x: i32,
4238 y: i32,
4239 z: i32,
4240 block_id: YogStr,
4241) -> bool {
4242 unsafe {
4243 host!(
4244 "dev/yog/YogCallbackChunkGenerator",
4245 "setBlockInGeneratingChunk",
4246 [I(x), I(y), I(z), S(block_id)]
4247 )
4248 .unwrap_or(false)
4249 }
4250}
4251
4252#[unsafe(no_mangle)]
4253pub extern "system" fn Java_dev_yog_NativeBridge_nativeGenerateChunk<'l>(
4254 mut env: JNIEnv<'l>,
4255 _class: JClass<'l>,
4256 generator_type_id: JString<'l>,
4257 chunk_x: jint,
4258 chunk_z: jint,
4259 min_y: jint,
4260 height: jint,
4261) {
4262 let id: String = match env.get_string(&generator_type_id) {
4263 Ok(s) => String::from(s),
4264 Err(_) => return,
4265 };
4266 let terrain = handlers().terrains.get(&id).cloned();
4267 if let Some(g) = terrain {
4268 guard("chunk_generator", || {
4269 g.generate(chunk_x, chunk_z, min_y, height, |x, y, z, block| unsafe {
4270 chunk_writer_set_block(std::ptr::null_mut(), x, y, z, YogStr::from_str(block));
4271 });
4272 });
4273 return;
4274 }
4275 let Some((module, ud, cb)) = handlers().chunk_generators.get(&id).cloned() else {
4276 return;
4277 };
4278 if module.is_retiring() {
4279 return;
4280 }
4281 let Some(_call) = module.enter() else { return };
4282 let writer = yog_abi::YogChunkWriterApi {
4283 chunk_x,
4284 chunk_z,
4285 min_y,
4286 height,
4287 ctx: std::ptr::null_mut(),
4288 set_block: chunk_writer_set_block,
4289 };
4290 guard("chunk_generator", || unsafe {
4291 cb(ud, &writer as *const _);
4292 });
4293}
4294
4295#[unsafe(no_mangle)]
4296pub extern "system" fn Java_dev_yog_NativeBridge_nativeTypedCommandSchemas<'l>(
4297 env: JNIEnv<'l>,
4298 _class: JClass<'l>,
4299) -> jstring {
4300 let mut lines = Vec::new();
4305 for (name, regs) in handlers().commands.iter() {
4306 let mut seen = std::collections::HashSet::new();
4307 for (_module, schema, _, _) in regs {
4308 if let Some(schema) = schema
4309 && seen.insert(schema.as_str())
4310 {
4311 lines.push(format!("{name}\t{schema}"));
4312 }
4313 }
4314 }
4315 env.new_string(lines.join("\n"))
4316 .map(jni::objects::JString::into_raw)
4317 .unwrap_or(std::ptr::null_mut())
4318}
4319
4320#[unsafe(no_mangle)]
4321pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnCommand<'l>(
4322 mut env: JNIEnv<'l>,
4323 _class: JClass<'l>,
4324 name: JString<'l>,
4325 args: JString<'l>,
4326 source: JString<'l>,
4327 uuid: JString<'l>,
4328 dimension: JString<'l>,
4329) -> jstring {
4330 let (n, a, s, u, d) = (
4331 env.get_string(&name).map(String::from).unwrap_or_default(),
4332 env.get_string(&args).map(String::from).unwrap_or_default(),
4333 env.get_string(&source)
4334 .map(String::from)
4335 .unwrap_or_default(),
4336 env.get_string(&uuid).map(String::from).unwrap_or_default(),
4337 env.get_string(&dimension)
4338 .map(String::from)
4339 .unwrap_or_default(),
4340 );
4341 let h = handlers();
4342 let srv = srv_ptr();
4343 let args: Vec<&str> = if a.is_empty() { Vec::new() } else { a.split('\t').collect() };
4344 let call = |ud: &*mut c_void, f: &YogCommandFn, args: &str| -> Option<String> {
4346 let ev = yog_abi::YogCommandEvent {
4347 name: YogStr::from_str(&n),
4348 args: YogStr::from_str(args),
4349 source: YogStr::from_str(&s),
4350 uuid: YogStr::from_str(&u),
4351 dimension: YogStr::from_str(&d),
4352 };
4353 let mut buf = [0u8; 4096];
4354 let mut reply_len: u32 = 0;
4355 unsafe { f(*ud, srv, &ev, buf.as_mut_ptr(), buf.len() as u32, &mut reply_len) };
4356 (reply_len > 0).then(|| String::from_utf8_lossy(&buf[..reply_len as usize]).into_owned())
4357 };
4358 let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4359 let Some(regs) = h.commands.get(&n) else {
4360 return String::new();
4361 };
4362 let live = regs.iter().filter(|(module, ..)| !module.is_retiring());
4363 for pass in 0..3 {
4368 for (module, schema, ud, f) in live.clone() {
4369 let joined;
4370 let these = match (pass, schema) {
4371 (0, Some(schema)) if schema.split_whitespace().count() == args.len() => a.as_str(),
4372 (1, Some(schema)) => {
4373 let types: Vec<&str> = schema.split_whitespace().collect();
4374 let k = types.len();
4375 if types.last() != Some(&"string") || k == 0 || args.len() <= k {
4376 continue;
4377 }
4378 let mut parts: Vec<String> = args[..k - 1].iter().map(|x| x.to_string()).collect();
4379 parts.push(args[k - 1..].join(" "));
4380 joined = parts.join("\t");
4381 joined.as_str()
4382 }
4383 (2, None) => a.as_str(),
4384 _ => continue,
4385 };
4386 let Some(_call) = module.enter() else {
4387 continue;
4388 };
4389 if let Some(r) = call(ud, f, these) {
4390 return r;
4391 }
4392 }
4393 }
4394 String::new()
4395 }))
4396 .unwrap_or_else(|_| {
4397 yog_logging::error!("a mod panicked handling command `{}`", n);
4398 String::new()
4399 });
4400
4401 env.new_string(reply)
4402 .map(jni::objects::JString::into_raw)
4403 .unwrap_or(std::ptr::null_mut())
4404}
4405
4406#[unsafe(no_mangle)]
4409pub extern "system" fn Java_dev_yog_NativeBridge_nativeContent<'l>(
4410 env: JNIEnv<'l>,
4411 _class: JClass<'l>,
4412) -> jni::sys::jbyteArray {
4413 let h = handlers();
4414 let bytes = {
4415 let mut cached = h.content_blob.lock().unwrap_or_else(|e| e.into_inner());
4416 cached.get_or_insert_with(|| std::sync::Arc::new(content_blob::blob(&h))).clone()
4417 };
4418 env.byte_array_from_slice(&bytes)
4419 .map(|a| a.into_raw())
4420 .unwrap_or(std::ptr::null_mut())
4421}
4422
4423#[unsafe(no_mangle)]
4424pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPacket<'l>(
4425 mut env: JNIEnv<'l>,
4426 _class: JClass<'l>,
4427 channel: JString<'l>,
4428 player: JString<'l>,
4429 payload: JByteArray<'l>,
4430) {
4431 let ch = env
4432 .get_string(&channel)
4433 .map(String::from)
4434 .unwrap_or_default();
4435 let pl = env
4436 .get_string(&player)
4437 .map(String::from)
4438 .unwrap_or_default();
4439 let data = env.convert_byte_array(&payload).unwrap_or_default();
4440 let ev = yog_abi::YogPacketEvent {
4441 channel: YogStr::from_str(&ch),
4442 player: YogStr::from_str(&pl),
4443 payload: data.as_ptr(),
4444 payload_len: data.len() as u32,
4445 };
4446 let h = handlers();
4447 let srv = srv_ptr();
4448 guard("on_packet", || {
4449 if let Some((module, ud, f)) = h.packets.get(&ch) {
4450 if module.is_retiring() {
4451 return;
4452 }
4453 let Some(_call) = module.enter() else { return };
4454 unsafe { f(*ud, srv, &ev) };
4455 }
4456 });
4457}
4458
4459#[unsafe(no_mangle)]
4460pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientPacket<'l>(
4461 mut env: JNIEnv<'l>,
4462 _class: JClass<'l>,
4463 channel: JString<'l>,
4464 payload: JByteArray<'l>,
4465) {
4466 let ch = env
4467 .get_string(&channel)
4468 .map(String::from)
4469 .unwrap_or_default();
4470 let data = env.convert_byte_array(&payload).unwrap_or_default();
4471 if ch == entity_data::CHANNEL {
4472 guard("entity data", || entity_data::receive(&data));
4473 return;
4474 }
4475 let ev = yog_abi::YogPacketEvent {
4476 channel: YogStr::from_str(&ch),
4477 player: YogStr::EMPTY,
4478 payload: data.as_ptr(),
4479 payload_len: data.len() as u32,
4480 };
4481 let h = handlers();
4482 let srv = srv_ptr();
4483 guard("on_client_packet", || {
4484 if let Some((module, ud, f)) = h.client_packets.get(&ch) {
4485 if module.is_retiring() {
4486 return;
4487 }
4488 let Some(_call) = module.enter() else { return };
4489 unsafe { f(*ud, srv, &ev) };
4490 }
4491 });
4492}
4493
4494#[unsafe(no_mangle)]
4495pub extern "system" fn Java_dev_yog_NativeBridge_nativePacketChannels<'l>(
4496 env: JNIEnv<'l>,
4497 _class: JClass<'l>,
4498) -> jstring {
4499 let s = handlers()
4500 .packets
4501 .keys()
4502 .cloned()
4503 .collect::<Vec<_>>()
4504 .join("\n");
4505 env.new_string(s)
4506 .map(jni::objects::JString::into_raw)
4507 .unwrap_or(std::ptr::null_mut())
4508}
4509
4510#[unsafe(no_mangle)]
4511pub extern "system" fn Java_dev_yog_NativeBridge_nativeClientPacketChannels<'l>(
4512 env: JNIEnv<'l>,
4513 _class: JClass<'l>,
4514) -> jstring {
4515 let s = handlers()
4516 .client_packets
4517 .keys()
4518 .cloned()
4519 .chain([entity_data::CHANNEL.to_owned()])
4520 .collect::<Vec<_>>()
4521 .join("\n");
4522 env.new_string(s)
4523 .map(jni::objects::JString::into_raw)
4524 .unwrap_or(std::ptr::null_mut())
4525}
4526
4527#[unsafe(no_mangle)]
4528pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawn<'l>(
4529 mut env: JNIEnv<'l>,
4530 _class: JClass<'l>,
4531 entity_type: JString<'l>,
4532 uuid: JString<'l>,
4533 dimension: JString<'l>,
4534) {
4535 let h = handlers();
4536 if h.entity_spawn.is_empty() {
4537 return;
4538 }
4539 let (et, u, d) = (
4540 jstr!(env, entity_type),
4541 jstr!(env, uuid),
4542 jstr!(env, dimension),
4543 );
4544 let ev = yog_abi::YogEntitySpawnEvent {
4545 entity_type: YogStr::from_str(&et),
4546 uuid: YogStr::from_str(&u),
4547 dimension: YogStr::from_str(&d),
4548 };
4549 let srv = srv_ptr();
4550 guard("on_entity_spawn", || {
4551 each(&h.entity_spawn, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4552 });
4553}
4554
4555#[unsafe(no_mangle)]
4556pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawnPre<'l>(
4557 mut env: JNIEnv<'l>,
4558 _class: JClass<'l>,
4559 entity_type: JString<'l>,
4560 uuid: JString<'l>,
4561 dimension: JString<'l>,
4562) -> jni::sys::jboolean {
4563 let h = handlers();
4564 if h.entity_spawn.is_empty() {
4565 return 1;
4566 }
4567 let et = jstr!(env, entity_type, 1);
4568 let u = jstr!(env, uuid, 1);
4569 let d = jstr!(env, dimension, 1);
4570 let ev = yog_abi::YogEntitySpawnEvent {
4571 entity_type: YogStr::from_str(&et),
4572 uuid: YogStr::from_str(&u),
4573 dimension: YogStr::from_str(&d),
4574 };
4575 let srv = srv_ptr();
4576 allowed(&h.entity_spawn, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4577}
4578
4579#[unsafe(no_mangle)]
4580pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamagePre<'l>(
4581 mut env: JNIEnv<'l>,
4582 _class: JClass<'l>,
4583 entity_type: JString<'l>,
4584 uuid: JString<'l>,
4585 amount: jfloat,
4586 source: JString<'l>,
4587 dimension: JString<'l>,
4588) -> jni::sys::jboolean {
4589 let h = handlers();
4590 if h.entity_damage.is_empty() {
4591 return 1;
4592 }
4593 let et = jstr!(env, entity_type, 1);
4594 let u = jstr!(env, uuid, 1);
4595 let s = jstr!(env, source, 1);
4596 let d = jstr!(env, dimension, 1);
4597 let ev = yog_abi::YogEntityDamageEvent {
4598 entity_type: YogStr::from_str(&et),
4599 uuid: YogStr::from_str(&u),
4600 amount,
4601 source: YogStr::from_str(&s),
4602 dimension: YogStr::from_str(&d),
4603 };
4604 let srv = srv_ptr();
4605 allowed(&h.entity_damage, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4606}
4607
4608#[unsafe(no_mangle)]
4609pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlockPre<'l>(
4610 mut env: JNIEnv<'l>,
4611 _class: JClass<'l>,
4612 player: JString<'l>,
4613 block: JString<'l>,
4614 x: jint,
4615 y: jint,
4616 z: jint,
4617 dimension: JString<'l>,
4618 sneaking: jni::sys::jboolean,
4619) -> jni::sys::jboolean {
4620 let h = handlers();
4621 if h.player_place_block.is_empty() {
4622 return 1;
4623 }
4624 let p = jstr!(env, player, 1);
4625 let b = jstr!(env, block, 1);
4626 let d = jstr!(env, dimension, 1);
4627 let ev = YogPlaceBlockEvent {
4628 player: YogStr::from_str(&p),
4629 block: YogStr::from_str(&b),
4630 pos: YogBlockPos { x, y, z },
4631 dimension: YogStr::from_str(&d),
4632 sneaking: sneaking != 0,
4633 };
4634 let srv = srv_ptr();
4635 allowed(&h.player_place_block, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4636}
4637
4638#[unsafe(no_mangle)]
4639pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlock<'l>(
4640 mut env: JNIEnv<'l>,
4641 _class: JClass<'l>,
4642 player: JString<'l>,
4643 block: JString<'l>,
4644 x: jint,
4645 y: jint,
4646 z: jint,
4647 dimension: JString<'l>,
4648 sneaking: jni::sys::jboolean,
4649) {
4650 let h = handlers();
4651 if h.player_place_block.is_empty() {
4652 return;
4653 }
4654 let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
4655 let ev = YogPlaceBlockEvent {
4656 player: YogStr::from_str(&p),
4657 block: YogStr::from_str(&b),
4658 pos: YogBlockPos { x, y, z },
4659 dimension: YogStr::from_str(&d),
4660 sneaking: sneaking != 0,
4661 };
4662 let srv = srv_ptr();
4663 guard("on_player_place_block", || {
4664 each(&h.player_place_block, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4665 });
4666}
4667
4668#[unsafe(no_mangle)]
4669pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeathPre<'l>(
4670 mut env: JNIEnv<'l>,
4671 _class: JClass<'l>,
4672 player: JString<'l>,
4673 uuid: JString<'l>,
4674 source: JString<'l>,
4675 dimension: JString<'l>,
4676) -> jni::sys::jboolean {
4677 let h = handlers();
4678 if h.player_death.is_empty() {
4679 return 1;
4680 }
4681 let p = jstr!(env, player, 1);
4682 let u = jstr!(env, uuid, 1);
4683 let s = jstr!(env, source, 1);
4684 let d = jstr!(env, dimension, 1);
4685 let ev = YogPlayerDeathEvent {
4686 player: YogStr::from_str(&p),
4687 uuid: YogStr::from_str(&u),
4688 source: YogStr::from_str(&s),
4689 dimension: YogStr::from_str(&d),
4690 };
4691 let srv = srv_ptr();
4692 allowed(&h.player_death, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4693}
4694
4695#[unsafe(no_mangle)]
4696pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeath<'l>(
4697 mut env: JNIEnv<'l>,
4698 _class: JClass<'l>,
4699 player: JString<'l>,
4700 uuid: JString<'l>,
4701 source: JString<'l>,
4702 dimension: JString<'l>,
4703) {
4704 let h = handlers();
4705 if h.player_death.is_empty() {
4706 return;
4707 }
4708 let (p, u, s, d) = (
4709 jstr!(env, player),
4710 jstr!(env, uuid),
4711 jstr!(env, source),
4712 jstr!(env, dimension),
4713 );
4714 let ev = YogPlayerDeathEvent {
4715 player: YogStr::from_str(&p),
4716 uuid: YogStr::from_str(&u),
4717 source: YogStr::from_str(&s),
4718 dimension: YogStr::from_str(&d),
4719 };
4720 let srv = srv_ptr();
4721 guard("on_player_death", || {
4722 each(&h.player_death, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4723 });
4724}
4725
4726#[unsafe(no_mangle)]
4727pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerRespawn<'l>(
4728 mut env: JNIEnv<'l>,
4729 _class: JClass<'l>,
4730 player: JString<'l>,
4731 uuid: JString<'l>,
4732 at_anchor: jni::sys::jboolean,
4733 dimension: JString<'l>,
4734) {
4735 let h = handlers();
4736 if h.player_respawn.is_empty() {
4737 return;
4738 }
4739 let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
4740 let ev = YogPlayerRespawnEvent {
4741 player: YogStr::from_str(&p),
4742 uuid: YogStr::from_str(&u),
4743 at_anchor: at_anchor != 0,
4744 dimension: YogStr::from_str(&d),
4745 };
4746 let srv = srv_ptr();
4747 guard("on_player_respawn", || {
4748 each(&h.player_respawn, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4749 });
4750}
4751
4752#[unsafe(no_mangle)]
4753pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAdvancement<'l>(
4754 mut env: JNIEnv<'l>,
4755 _class: JClass<'l>,
4756 player: JString<'l>,
4757 uuid: JString<'l>,
4758 advancement: JString<'l>,
4759 dimension: JString<'l>,
4760) {
4761 let h = handlers();
4762 if h.advancement.is_empty() {
4763 return;
4764 }
4765 let (p, u, a, d) = (
4766 jstr!(env, player),
4767 jstr!(env, uuid),
4768 jstr!(env, advancement),
4769 jstr!(env, dimension),
4770 );
4771 let ev = YogAdvancementEvent {
4772 player: YogStr::from_str(&p),
4773 uuid: YogStr::from_str(&u),
4774 advancement: YogStr::from_str(&a),
4775 dimension: YogStr::from_str(&d),
4776 };
4777 let srv = srv_ptr();
4778 guard("on_advancement", || {
4779 each(&h.advancement, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4780 });
4781}
4782
4783#[unsafe(no_mangle)]
4784pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteractPre<'l>(
4785 mut env: JNIEnv<'l>,
4786 _class: JClass<'l>,
4787 player: JString<'l>,
4788 player_uuid: JString<'l>,
4789 entity_type: JString<'l>,
4790 entity_uuid: JString<'l>,
4791 hand: JString<'l>,
4792 dimension: JString<'l>,
4793) -> jni::sys::jboolean {
4794 let h = handlers();
4795 if h.entity_interact.is_empty() {
4796 return 1;
4797 }
4798 let p = jstr!(env, player, 1);
4799 let pu = jstr!(env, player_uuid, 1);
4800 let et = jstr!(env, entity_type, 1);
4801 let eu = jstr!(env, entity_uuid, 1);
4802 let ha = jstr!(env, hand, 1);
4803 let d = jstr!(env, dimension, 1);
4804 let ev = YogEntityInteractEvent {
4805 player: YogStr::from_str(&p),
4806 player_uuid: YogStr::from_str(&pu),
4807 entity_type: YogStr::from_str(&et),
4808 entity_uuid: YogStr::from_str(&eu),
4809 hand: YogStr::from_str(&ha),
4810 dimension: YogStr::from_str(&d),
4811 };
4812 let srv = srv_ptr();
4813 allowed(&h.entity_interact, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4814}
4815
4816#[unsafe(no_mangle)]
4817pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteract<'l>(
4818 mut env: JNIEnv<'l>,
4819 _class: JClass<'l>,
4820 player: JString<'l>,
4821 player_uuid: JString<'l>,
4822 entity_type: JString<'l>,
4823 entity_uuid: JString<'l>,
4824 hand: JString<'l>,
4825 dimension: JString<'l>,
4826) {
4827 let h = handlers();
4828 if h.entity_interact.is_empty() {
4829 return;
4830 }
4831 let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
4832 let (et, eu, ha, d) = (
4833 jstr!(env, entity_type),
4834 jstr!(env, entity_uuid),
4835 jstr!(env, hand),
4836 jstr!(env, dimension),
4837 );
4838 let ev = YogEntityInteractEvent {
4839 player: YogStr::from_str(&p),
4840 player_uuid: YogStr::from_str(&pu),
4841 entity_type: YogStr::from_str(&et),
4842 entity_uuid: YogStr::from_str(&eu),
4843 hand: YogStr::from_str(&ha),
4844 dimension: YogStr::from_str(&d),
4845 };
4846 let srv = srv_ptr();
4847 guard("on_entity_interact", || {
4848 each(&h.entity_interact, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4849 });
4850}
4851
4852#[unsafe(no_mangle)]
4853pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemCraft<'l>(
4854 mut env: JNIEnv<'l>,
4855 _class: JClass<'l>,
4856 player: JString<'l>,
4857 player_uuid: JString<'l>,
4858 result_item: JString<'l>,
4859 result_count: jint,
4860 dimension: JString<'l>,
4861) {
4862 let h = handlers();
4863 if h.item_craft.is_empty() {
4864 return;
4865 }
4866 let (p, pu, ri, d) = (
4867 jstr!(env, player),
4868 jstr!(env, player_uuid),
4869 jstr!(env, result_item),
4870 jstr!(env, dimension),
4871 );
4872 let ev = YogCraftEvent {
4873 player: YogStr::from_str(&p),
4874 player_uuid: YogStr::from_str(&pu),
4875 result_item: YogStr::from_str(&ri),
4876 result_count: result_count as u32,
4877 dimension: YogStr::from_str(&d),
4878 };
4879 let srv = srv_ptr();
4880 guard("on_item_craft", || {
4881 each(&h.item_craft, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4882 });
4883}
4884
4885#[unsafe(no_mangle)]
4886pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosionPre<'l>(
4887 mut env: JNIEnv<'l>,
4888 _class: JClass<'l>,
4889 dimension: JString<'l>,
4890 x: jdouble,
4891 y: jdouble,
4892 z: jdouble,
4893 power: jfloat,
4894 cause_uuid: JString<'l>,
4895) -> jni::sys::jboolean {
4896 let h = handlers();
4897 if h.explosion.is_empty() {
4898 return 1;
4899 }
4900 let d = jstr!(env, dimension, 1);
4901 let cu = jstr!(env, cause_uuid, 1);
4902 let ev = YogExplosionEvent {
4903 dimension: YogStr::from_str(&d),
4904 x,
4905 y,
4906 z,
4907 power,
4908 cause_uuid: YogStr::from_str(&cu),
4909 };
4910 let srv = srv_ptr();
4911 allowed(&h.explosion, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4912}
4913
4914#[unsafe(no_mangle)]
4915pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosion<'l>(
4916 mut env: JNIEnv<'l>,
4917 _class: JClass<'l>,
4918 dimension: JString<'l>,
4919 x: jdouble,
4920 y: jdouble,
4921 z: jdouble,
4922 power: jfloat,
4923 cause_uuid: JString<'l>,
4924) {
4925 let h = handlers();
4926 if h.explosion.is_empty() {
4927 return;
4928 }
4929 let (d, cu) = (jstr!(env, dimension), jstr!(env, cause_uuid));
4930 let ev = YogExplosionEvent {
4931 dimension: YogStr::from_str(&d),
4932 x,
4933 y,
4934 z,
4935 power,
4936 cause_uuid: YogStr::from_str(&cu),
4937 };
4938 let srv = srv_ptr();
4939 guard("on_explosion", || {
4940 each(&h.explosion, |ud, f| unsafe { f(ud, srv, &ev, 1) });
4941 });
4942}
4943
4944#[unsafe(no_mangle)]
4949pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemInsert<'l>(
4950 mut env: JNIEnv<'l>,
4951 _class: JClass<'l>,
4952 block: JString<'l>,
4953 x: jint,
4954 y: jint,
4955 z: jint,
4956 dimension: JString<'l>,
4957 item_id: JString<'l>,
4958 count: jint,
4959 nbt: JString<'l>,
4960 simulate: jni::sys::jboolean,
4961) -> jni::sys::jboolean {
4962 let h = handlers();
4963 if h.item_insert.is_empty() {
4964 return 0;
4965 }
4966 let (b, d, i, n) = (jstr!(env, block, 0), jstr!(env, dimension, 0), jstr!(env, item_id, 0), jstr!(env, nbt, 0));
4967 let ev = YogItemInsertEvent {
4968 block: YogStr::from_str(&b),
4969 pos: YogBlockPos { x, y, z },
4970 dimension: YogStr::from_str(&d),
4971 item_id: YogStr::from_str(&i),
4972 count: count.max(0) as u32,
4973 nbt: YogStr::from_str(&n),
4974 };
4975 let srv = srv_ptr();
4976 if simulate != 0 {
4977 !allowed(&h.item_insert, |ud, f| unsafe { !f(ud, srv, &ev, 0) }) as jni::sys::jboolean
4979 } else {
4980 let mut taken = false;
4981 guard("on_item_insert", || {
4982 each(&h.item_insert, |ud, f| taken |= unsafe { f(ud, srv, &ev, 1) });
4983 });
4984 taken as jni::sys::jboolean
4985 }
4986}
4987
4988#[unsafe(no_mangle)]
4991pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickupPre<'l>(
4992 mut env: JNIEnv<'l>,
4993 _class: JClass<'l>,
4994 player: JString<'l>,
4995 player_uuid: JString<'l>,
4996 item_id: JString<'l>,
4997 item_count: jint,
4998 entity_uuid: JString<'l>,
4999 dimension: JString<'l>,
5000) -> jni::sys::jboolean {
5001 let h = handlers();
5002 if h.item_pickup.is_empty() {
5003 return 1;
5004 }
5005 let p = jstr!(env, player, 1);
5006 let pu = jstr!(env, player_uuid, 1);
5007 let ii = jstr!(env, item_id, 1);
5008 let eu = jstr!(env, entity_uuid, 1);
5009 let d = jstr!(env, dimension, 1);
5010 let ev = YogItemPickupEvent {
5011 player: YogStr::from_str(&p),
5012 player_uuid: YogStr::from_str(&pu),
5013 item_id: YogStr::from_str(&ii),
5014 item_count: item_count as u32,
5015 entity_uuid: YogStr::from_str(&eu),
5016 dimension: YogStr::from_str(&d),
5017 };
5018 let srv = srv_ptr();
5019 allowed(&h.item_pickup, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
5020}
5021
5022#[unsafe(no_mangle)]
5023pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickup<'l>(
5024 mut env: JNIEnv<'l>,
5025 _class: JClass<'l>,
5026 player: JString<'l>,
5027 player_uuid: JString<'l>,
5028 item_id: JString<'l>,
5029 item_count: jint,
5030 entity_uuid: JString<'l>,
5031 dimension: JString<'l>,
5032) {
5033 let h = handlers();
5034 if h.item_pickup.is_empty() {
5035 return;
5036 }
5037 let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
5038 let (ii, eu, d) = (
5039 jstr!(env, item_id),
5040 jstr!(env, entity_uuid),
5041 jstr!(env, dimension),
5042 );
5043 let ev = YogItemPickupEvent {
5044 player: YogStr::from_str(&p),
5045 player_uuid: YogStr::from_str(&pu),
5046 item_id: YogStr::from_str(&ii),
5047 item_count: item_count as u32,
5048 entity_uuid: YogStr::from_str(&eu),
5049 dimension: YogStr::from_str(&d),
5050 };
5051 let srv = srv_ptr();
5052 guard("on_item_pickup", || {
5053 each(&h.item_pickup, |ud, f| unsafe { f(ud, srv, &ev, 1) });
5054 });
5055}
5056
5057#[unsafe(no_mangle)]
5058pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerMove<'l>(
5059 mut env: JNIEnv<'l>,
5060 _class: JClass<'l>,
5061 player: JString<'l>,
5062 player_uuid: JString<'l>,
5063 x: jdouble,
5064 y: jdouble,
5065 z: jdouble,
5066 yaw: jfloat,
5067 pitch: jfloat,
5068 dimension: JString<'l>,
5069) {
5070 let h = handlers();
5071 if h.player_move.is_empty() {
5072 return;
5073 }
5074 let (p, pu, d) = (
5075 jstr!(env, player),
5076 jstr!(env, player_uuid),
5077 jstr!(env, dimension),
5078 );
5079 let ev = YogPlayerMoveEvent {
5080 player: YogStr::from_str(&p),
5081 player_uuid: YogStr::from_str(&pu),
5082 x,
5083 y,
5084 z,
5085 yaw,
5086 pitch,
5087 dimension: YogStr::from_str(&d),
5088 };
5089 let srv = srv_ptr();
5090 guard("on_player_move", || {
5091 each(&h.player_move, |ud, f| unsafe { f(ud, srv, &ev, 1) });
5092 });
5093}
5094
5095#[unsafe(no_mangle)]
5096pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpenPre<'l>(
5097 mut env: JNIEnv<'l>,
5098 _class: JClass<'l>,
5099 player: JString<'l>,
5100 player_uuid: JString<'l>,
5101 dimension: JString<'l>,
5102) -> jni::sys::jboolean {
5103 let h = handlers();
5104 if h.container_open.is_empty() {
5105 return 1;
5106 }
5107 let p = jstr!(env, player, 1);
5108 let pu = jstr!(env, player_uuid, 1);
5109 let d = jstr!(env, dimension, 1);
5110 let ev = YogContainerOpenEvent {
5111 player: YogStr::from_str(&p),
5112 player_uuid: YogStr::from_str(&pu),
5113 container_type: YogStr::EMPTY,
5114 dimension: YogStr::from_str(&d),
5115 };
5116 let srv = srv_ptr();
5117 allowed(&h.container_open, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
5118}
5119
5120#[unsafe(no_mangle)]
5121pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpen<'l>(
5122 mut env: JNIEnv<'l>,
5123 _class: JClass<'l>,
5124 player: JString<'l>,
5125 player_uuid: JString<'l>,
5126 container_type: JString<'l>,
5127 dimension: JString<'l>,
5128) {
5129 let h = handlers();
5130 if h.container_open.is_empty() {
5131 return;
5132 }
5133 let (p, pu, ct, d) = (
5134 jstr!(env, player),
5135 jstr!(env, player_uuid),
5136 jstr!(env, container_type),
5137 jstr!(env, dimension),
5138 );
5139 let ev = YogContainerOpenEvent {
5140 player: YogStr::from_str(&p),
5141 player_uuid: YogStr::from_str(&pu),
5142 container_type: YogStr::from_str(&ct),
5143 dimension: YogStr::from_str(&d),
5144 };
5145 let srv = srv_ptr();
5146 guard("on_container_open", || {
5147 each(&h.container_open, |ud, f| unsafe { f(ud, srv, &ev, 1) });
5148 });
5149}
5150
5151#[unsafe(no_mangle)]
5152pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerClose<'l>(
5153 mut env: JNIEnv<'l>,
5154 _class: JClass<'l>,
5155 player: JString<'l>,
5156 player_uuid: JString<'l>,
5157 dimension: JString<'l>,
5158) {
5159 let h = handlers();
5160 if h.container_close.is_empty() {
5161 return;
5162 }
5163 let (p, pu, d) = (
5164 jstr!(env, player),
5165 jstr!(env, player_uuid),
5166 jstr!(env, dimension),
5167 );
5168 let ev = YogContainerCloseEvent {
5169 player: YogStr::from_str(&p),
5170 player_uuid: YogStr::from_str(&pu),
5171 dimension: YogStr::from_str(&d),
5172 };
5173 let srv = srv_ptr();
5174 guard("on_container_close", || {
5175 each(&h.container_close, |ud, f| unsafe { f(ud, srv, &ev, 1) });
5176 });
5177}
5178
5179#[unsafe(no_mangle)]
5180pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHitPre<'l>(
5181 mut env: JNIEnv<'l>,
5182 _class: JClass<'l>,
5183 projectile_type: JString<'l>,
5184 projectile_uuid: JString<'l>,
5185 shooter_uuid: JString<'l>,
5186 hit_type: JString<'l>,
5187 hit_entity_uuid: JString<'l>,
5188 x: jdouble,
5189 y: jdouble,
5190 z: jdouble,
5191 dimension: JString<'l>,
5192) -> jni::sys::jboolean {
5193 let h = handlers();
5194 if h.projectile_hit.is_empty() {
5195 return 1;
5196 }
5197 let pt = jstr!(env, projectile_type, 1);
5198 let pu = jstr!(env, projectile_uuid, 1);
5199 let su = jstr!(env, shooter_uuid, 1);
5200 let ht = jstr!(env, hit_type, 1);
5201 let heu = jstr!(env, hit_entity_uuid, 1);
5202 let dim = jstr!(env, dimension, 1);
5203 let ev = YogProjectileHitEvent {
5204 projectile_type: YogStr::from_str(&pt),
5205 projectile_uuid: YogStr::from_str(&pu),
5206 shooter_uuid: YogStr::from_str(&su),
5207 hit_type: YogStr::from_str(&ht),
5208 hit_entity_uuid: YogStr::from_str(&heu),
5209 x,
5210 y,
5211 z,
5212 dimension: YogStr::from_str(&dim),
5213 };
5214 let srv = srv_ptr();
5215 allowed(&h.projectile_hit, |ud, f| unsafe { f(ud, srv, &ev, 0) }) as jni::sys::jboolean
5216}
5217
5218#[unsafe(no_mangle)]
5219pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHit<'l>(
5220 mut env: JNIEnv<'l>,
5221 _class: JClass<'l>,
5222 projectile_type: JString<'l>,
5223 projectile_uuid: JString<'l>,
5224 shooter_uuid: JString<'l>,
5225 hit_type: JString<'l>,
5226 hit_entity_uuid: JString<'l>,
5227 x: jdouble,
5228 y: jdouble,
5229 z: jdouble,
5230 dimension: JString<'l>,
5231) {
5232 let h = handlers();
5233 if h.projectile_hit.is_empty() {
5234 return;
5235 }
5236 let (pt, pu) = (jstr!(env, projectile_type), jstr!(env, projectile_uuid));
5237 let (su, ht) = (jstr!(env, shooter_uuid), jstr!(env, hit_type));
5238 let (heu, dim) = (jstr!(env, hit_entity_uuid), jstr!(env, dimension));
5239 let ev = YogProjectileHitEvent {
5240 projectile_type: YogStr::from_str(&pt),
5241 projectile_uuid: YogStr::from_str(&pu),
5242 shooter_uuid: YogStr::from_str(&su),
5243 hit_type: YogStr::from_str(&ht),
5244 hit_entity_uuid: YogStr::from_str(&heu),
5245 x,
5246 y,
5247 z,
5248 dimension: YogStr::from_str(&dim),
5249 };
5250 let srv = srv_ptr();
5251 guard("on_projectile_hit", || {
5252 each(&h.projectile_hit, |ud, f| unsafe { f(ud, srv, &ev, 1) });
5253 });
5254}
5255
5256#[unsafe(no_mangle)]
5259pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientTick<'l>(
5260 _env: JNIEnv<'l>,
5261 _class: JClass<'l>,
5262) {
5263 let h = handlers();
5264 if h.client_tick.is_empty() {
5265 return;
5266 }
5267 guard("on_client_tick", || {
5268 each(&h.client_tick, |ud, f| unsafe { f(ud) });
5269 });
5270}
5271
5272fn gl_proc_address(sym: &str) -> usize {
5274 let ptr: Option<i64> = unsafe { bridge!("glProcAddress", [S(YogStr::from_str(sym))]) };
5275 ptr.unwrap_or(0) as usize
5276}
5277
5278#[unsafe(no_mangle)]
5279pub extern "system" fn Java_dev_yog_NativeBridge_nativeGlInit<'l>(
5280 _env: JNIEnv<'l>,
5281 _class: JClass<'l>,
5282) {
5283 if GL.get().is_some() {
5284 return;
5285 }
5286 let mut raw_get_binary: usize = 0;
5287 let mut raw_prog_binary: usize = 0;
5288 let gl = unsafe {
5289 glow::Context::from_loader_function(|sym| {
5290 let ptr = gl_proc_address(sym);
5291 match sym {
5293 "glGetProgramBinary" => raw_get_binary = ptr,
5294 "glProgramBinary" => raw_prog_binary = ptr,
5295 _ => {}
5296 }
5297 ptr as *const _
5298 })
5299 };
5300 let _ = GL.set(GlCtx(gl));
5301 let _ = GL_GET_PROGRAM_BINARY.set((raw_get_binary != 0).then_some(raw_get_binary));
5302 let _ = GL_PROGRAM_BINARY.set((raw_prog_binary != 0).then_some(raw_prog_binary));
5303 let get_program_iv = gl_proc_address("glGetProgramiv");
5306 let _ = GL_GET_PROGRAM_IV.set((get_program_iv != 0).then_some(get_program_iv));
5307}
5308
5309#[unsafe(no_mangle)]
5310pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnHudRender<'l>(
5311 _env: JNIEnv<'l>,
5312 _class: JClass<'l>,
5313 delta_tick: jfloat,
5314 screen_w: jint,
5315 screen_h: jint,
5316 scale_factor: jfloat,
5317 player_x: jfloat,
5318 player_y: jfloat,
5319 player_z: jfloat,
5320) {
5321 let h = handlers();
5322 let mut gfx = GFX_FN_TABLE;
5323 gfx.screen_w = screen_w;
5324 gfx.screen_h = screen_h;
5325 gfx.delta_tick = delta_tick;
5326 gfx.scale_factor = scale_factor;
5327 gfx.player_pos = [player_x, player_y, player_z];
5328
5329 if !h.hud_render.is_empty() {
5331 guard("on_hud_render", || {
5332 each(&h.hud_render, |ud, f| unsafe { f(ud, &gfx) });
5333 });
5334 }
5335
5336 let active_layers: Vec<UiLayer> = h.active_uis.lock().unwrap().clone();
5338 let sw = screen_w as f32;
5339 let sh = screen_h as f32;
5340 let ctx = unsafe { yog_gfx::GfxContext::from_raw(&gfx as *const _) };
5341 let mut renderers = h.book_renderers.lock().unwrap();
5342 for layer in &active_layers {
5343 if !layer.visible {
5344 continue;
5345 }
5346 if let Some(renderer) = renderers.get_mut(&layer.id) {
5347 renderer.render(&ctx, sw, sh);
5348 }
5349 }
5350}
5351
5352#[unsafe(no_mangle)]
5353pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnWorldRender<'l>(
5354 env: JNIEnv<'l>,
5355 _class: JClass<'l>,
5356 delta_tick: jfloat,
5357 screen_w: jint,
5358 screen_h: jint,
5359 scale_factor: jfloat,
5360 view_proj_arr: JFloatArray<'l>,
5361 cam_x: jfloat,
5362 cam_y: jfloat,
5363 cam_z: jfloat,
5364 player_x: jfloat,
5365 player_y: jfloat,
5366 player_z: jfloat,
5367) {
5368 let h = handlers();
5369 if h.world_render.is_empty() {
5370 return;
5371 }
5372 let mut view_proj = [0f32; 16];
5373 if env
5374 .get_float_array_region(&view_proj_arr, 0, &mut view_proj)
5375 .is_err()
5376 {
5377 return;
5378 }
5379 let mut gfx = GFX_FN_TABLE;
5380 gfx.screen_w = screen_w;
5381 gfx.screen_h = screen_h;
5382 gfx.delta_tick = delta_tick;
5383 gfx.scale_factor = scale_factor;
5384 gfx.view_proj = view_proj;
5385 gfx.camera_pos = [cam_x, cam_y, cam_z];
5386 gfx.player_pos = [player_x, player_y, player_z];
5387 guard("on_world_render", || {
5388 each(&h.world_render, |ud, f| unsafe { f(ud, &gfx) });
5389 });
5390}
5391
5392#[unsafe(no_mangle)]
5393pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnKeyPress<'l>(
5394 _env: JNIEnv<'l>,
5395 _class: JClass<'l>,
5396 key_code: jint,
5397 scan_code: jint,
5398 action: jint,
5399 modifiers: jint,
5400) -> jni::sys::jboolean {
5401 let h = handlers();
5402 if h.key_press.is_empty() {
5403 return 1;
5404 }
5405 let ev = YogKeyPressEvent {
5406 key_code,
5407 scan_code,
5408 action,
5409 modifiers,
5410 };
5411 allowed(&h.key_press, |ud, f| unsafe { f(ud, &ev) }) as jni::sys::jboolean
5412}
5413
5414#[unsafe(no_mangle)]
5415pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenOpen<'l>(
5416 mut env: JNIEnv<'l>,
5417 _class: JClass<'l>,
5418 screen_class: JString<'l>,
5419) {
5420 let h = handlers();
5421 if h.screen_open.is_empty() {
5422 return;
5423 }
5424 let sc = match env.get_string(&screen_class) {
5425 Ok(s) => String::from(s),
5426 Err(_) => return,
5427 };
5428 guard("on_screen_open", || {
5429 each(&h.screen_open, |ud, f| unsafe {
5430 f(ud, YogStr::from_str(&sc))
5431 });
5432 });
5433}
5434
5435#[unsafe(no_mangle)]
5436pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenClose<'l>(
5437 mut env: JNIEnv<'l>,
5438 _class: JClass<'l>,
5439 screen_class: JString<'l>,
5440) {
5441 let h = handlers();
5442 if h.screen_close.is_empty() {
5443 return;
5444 }
5445 let sc = match env.get_string(&screen_class) {
5446 Ok(s) => String::from(s),
5447 Err(_) => return,
5448 };
5449 guard("on_screen_close", || {
5450 each(&h.screen_close, |ud, f| unsafe {
5451 f(ud, YogStr::from_str(&sc))
5452 });
5453 });
5454}
5455
5456mod body;
5457mod entity_data;
5458mod client_api;
5459mod content_blob;
5460mod machine;
5461mod control_socket;
5462mod images;
5463mod svg;
5464mod jcall;
5465mod ui_jni;
5466#[cfg(feature = "bench")]
5468#[doc(hidden)]
5469pub mod bench_support;
5470#[cfg(test)]
5471mod tests;