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