Skip to main content

yog_api/
lib.rs

1//! Yog API — the single crate mod authors depend on.
2//!
3//! A facade that re-exports every Yog domain plus the central [`Registry`] hub.
4//! Add a new domain crate, re-export it here, and mods pick it up via
5//! `yog_api::*`. Items are available both flat (`yog_api::Registry`) and
6//! namespaced by domain (`yog_api::world::World`).
7
8mod interop;
9mod registry;
10
11pub use interop::Interop;
12pub use registry::{installed_mods, open_ui, server, CServer, Mod, ModInfo, Registry};
13pub use yog_gfx::{GfxContext, core as gfx_core, gl as gfx_gl, draw2d as gfx_draw2d};
14
15/// Stable C ABI — re-exported so mods don't need a direct `yog-abi` dependency.
16pub use yog_abi::{ABI_VERSION, YogApi};
17
18/// Inter-mod communication proc-macros.
19pub use yog_interop::{yog_export, import};
20
21/// rkyv — zero-copy serialization for interop. Re-exported so generated
22/// code and mods can use `rkyv::Archive`, `rkyv::Serialize`, `rkyv::Deserialize`
23/// without a direct dependency.
24pub use rkyv;
25
26#[doc(hidden)]
27pub use std::os::raw::c_void as __c_void;
28
29/// Export a [`Mod`] as a dynamically loadable Yog mod.
30///
31/// Generates the two C-ABI entry points the runtime looks up:
32/// - `yog_abi_version() -> u32`  — version check before loading
33/// - `yog_mod_register(*const YogApi, *const c_char)` — registration entry point,
34///   receives the mod's `id` from its manifest
35///
36/// Put this once at the crate root of a `cdylib` mod:
37///
38/// ```ignore
39/// yog_api::export_mod!(MyMod);
40/// ```
41#[macro_export]
42macro_rules! export_mod {
43    ($mod_ty:ty) => {
44        #[no_mangle]
45        pub extern "C" fn yog_abi_version() -> u32 {
46            $crate::ABI_VERSION
47        }
48
49        #[no_mangle]
50        pub unsafe extern "C" fn yog_mod_register(
51            api: *const $crate::YogApi,
52            mod_id_ptr: *const ::std::os::raw::c_char,
53        ) {
54            // Parse mod_id from the C string passed by the runtime.
55            let mod_id: &str = if mod_id_ptr.is_null() {
56                "unknown"
57            } else {
58                match ::std::ffi::CStr::from_ptr(mod_id_ptr).to_str() {
59                    Ok(s) => s,
60                    Err(_) => "unknown",
61                }
62            };
63            // Store for interop use (yog_api::interop::current_mod_id()).
64            $crate::__set_current_mod_id(mod_id);
65
66            // Catch panics so they never unwind across this `extern "C"` boundary
67            // back into the runtime (which would be undefined behaviour).
68            let outcome = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
69                // SAFETY: the runtime passes a valid YogApi pointer, verified via
70                // yog_abi_version() and abi_version/size checks before this call.
71                let mut registry = unsafe { $crate::Registry::from_raw(api) };
72                <$mod_ty as $crate::Mod>::register(&mut registry);
73
74                // Auto-register all #[yog_export] functions.
75                for (name, ptr) in $crate::__yog_export_registry().lock().unwrap().iter() {
76                    registry.interop().export(name, *ptr as *const ::std::os::raw::c_void);
77                }
78
79                // Resolve pending imports (may partially succeed — runtime
80                // does a final pass after all mods loaded).
81                $crate::__yog_resolve_pending_imports(&registry);
82            }));
83            if outcome.is_err() {
84                $crate::error!("mod {} panicked during register", ::core::stringify!($mod_ty));
85            }
86        }
87
88        /// Called by the runtime after ALL mods are loaded — resolves any
89        /// imports that weren't satisfied during initial registration.
90        #[no_mangle]
91        pub unsafe extern "C" fn __yog_resolve_imports_final(
92            api: *const $crate::YogApi,
93        ) {
94            let registry = unsafe { $crate::Registry::from_raw(api) };
95            $crate::__yog_resolve_pending_imports(&registry);
96        }
97    };
98}
99
100/// Internal: set by `export_mod!` before calling `Mod::register`.
101/// Used by `yog_api::interop::current_mod_id()` so `Interop::export` knows
102/// which mod is calling.
103#[doc(hidden)]
104pub fn __set_current_mod_id(id: &str) {
105    CURRENT_MOD_ID.with(|cell| cell.replace(Some(id.to_string())));
106}
107
108/// Internal: the current mod's id, set during `yog_mod_register`.
109#[doc(hidden)]
110pub fn __current_mod_id() -> Option<String> {
111    CURRENT_MOD_ID.with(|cell| cell.borrow().clone())
112}
113
114std::thread_local! {
115    static CURRENT_MOD_ID: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
116}
117
118// ── Auto-export registry (used by #[yog_export] proc-macro) ──────────────
119
120/// Global registry of `#[yog_export]` functions.
121/// Populated by static initializers before `yog_mod_register` runs.
122#[doc(hidden)]
123pub fn __yog_export_registry() -> &'static std::sync::Mutex<Vec<(&'static str, usize)>> {
124    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<(&'static str, usize)>>> =
125        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
126    &REG
127}
128
129/// Global registry of pending imports — populated by `import!` static initializers,
130/// resolved by `__yog_resolve_imports` when the runtime calls it after all mods loaded.
131#[doc(hidden)]
132pub fn __yog_pending_imports() -> &'static std::sync::Mutex<Vec<(&'static str, &'static str, usize)>> {
133    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<(&'static str, &'static str, usize)>>> =
134        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
135    &REG
136}
137
138/// Called by `export_mod!` after registration. Iterates pending imports and
139/// resolves them via the interop table. Also exported as `#[no_mangle]` so the
140/// runtime can call it after all mods are loaded (final resolution pass).
141#[doc(hidden)]
142pub unsafe fn __yog_resolve_pending_imports(registry: &crate::Registry) {
143    let interop = registry.interop();
144    for (mod_id, symbol, bind_fn_ptr) in __yog_pending_imports().lock().unwrap().iter() {
145        if let Some(ptr) = interop.import_raw(mod_id, symbol) {
146            let bind: unsafe extern "C" fn(*const std::ffi::c_void) = std::mem::transmute(*bind_fn_ptr);
147            bind(ptr);
148        }
149    }
150}
151
152pub use yog_command::CommandContext;
153pub use yog_core::{BlockPos, Server};
154pub use yog_event::{
155    AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
156    ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
157    EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent,
158    ItemPickupEvent, KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent,
159    PlayerLeaveEvent, PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent,
160    UseBlockEvent, UseItemEvent,
161};
162pub use yog_entity::Entity;
163pub use yog_network::{Packet, PacketEvent, PacketField};
164#[doc(inline)]
165pub use yog_network::packet;
166pub use yog_player::Player;
167pub use yog_registry::{BlockDef, FoodDef, FurnaceRecipe, ItemDef, ShapedRecipe, ShapelessRecipe, BookRecipe, ItemModifier, AdvancementReward, StartupGrant};
168pub use yog_config::Config;
169pub use yog_storage::{Storage, StorageScope, Value};
170pub use yog_world::World;
171pub use yog_book::{Book, BookCategory, BookEntry, BookPage, BookMacro, BookRegistry};
172pub use yog_book::{BookRenderer, BookFontRegistry};
173pub use yog_book::{text_page, text_page_titled, spotlight_page, crafting_page, smelting_page, image_page, entity_page, relations_page, pattern_page};
174pub use yog_ui::{UiRoot, LayoutNode, Rect, widget, Align, FlexDir, Dock, FocusStyle};
175pub use yog_inventory::{InventoryDef, SlotLayout};
176
177/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
178pub use yog_logging::{error, info, warn};
179
180/// Core types and handles.
181pub mod core {
182    pub use yog_core::*;
183}
184
185/// Events and the subscription registry.
186pub mod event {
187    pub use yog_event::*;
188}
189
190/// World access (block get/set, dimensions).
191pub mod world {
192    pub use yog_world::*;
193}
194
195/// Entity access (teleport, position, health, ... by UUID).
196pub mod entity {
197    pub use yog_entity::*;
198}
199
200/// Player access (give item, teleport).
201pub mod player {
202    pub use yog_player::*;
203}
204
205/// Content registration (custom items / blocks / food).
206pub mod content {
207    pub use yog_registry::*;
208}
209
210/// Networking (raw-byte packets over channels).
211pub mod network {
212    pub use yog_network::*;
213}
214
215/// Commands.
216pub mod command {
217    pub use yog_command::*;
218}
219
220/// Persistent key-value storage for mod data.
221pub mod storage {
222    pub use yog_storage::*;
223}
224
225/// Mod configuration (typed key/value files).
226pub mod config {
227    pub use yog_config::*;
228}
229
230/// In-game book/documentation system (Patchouli-like).
231pub mod book {
232    pub use yog_book::*;
233}
234
235/// UI framework — flexbox layout + widgets on top of yog-gfx.
236pub mod ui {
237    pub use yog_ui::*;
238}
239
240/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
241/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
242pub mod inventory {
243    pub use yog_inventory::*;
244}