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