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