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 (linkme distributed slice).
75                for entry in $crate::__yog_export_registry().lock().unwrap().iter() {
76                    registry.interop().export(entry.name, entry.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 ─────────────────────────────────────────────────
119
120#[doc(hidden)]
121pub struct YogExportEntry { pub name: &'static str, pub ptr: usize }
122
123#[doc(hidden)]
124pub struct YogImportEntry { pub mod_id: &'static str, pub symbol: &'static str, pub bind_fn: usize }
125
126#[doc(hidden)]
127pub fn __yog_export_registry() -> &'static std::sync::Mutex<Vec<YogExportEntry>> {
128    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogExportEntry>>> =
129        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
130    &REG
131}
132
133#[doc(hidden)]
134pub fn __yog_import_registry() -> &'static std::sync::Mutex<Vec<YogImportEntry>> {
135    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogImportEntry>>> =
136        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
137    &REG
138}
139
140/// Called by `export_mod!` after registration. Iterates pending imports and
141/// resolves them via the interop table. Also exported as `#[no_mangle]` so the
142/// runtime can call it after all mods are loaded (final resolution pass).
143#[doc(hidden)]
144pub unsafe fn __yog_resolve_pending_imports(registry: &crate::Registry) {
145    let interop = registry.interop();
146    for entry in __yog_import_registry().lock().unwrap().iter() {
147        if let Some(ptr) = interop.import_raw(entry.mod_id, entry.symbol) {
148            let bind: unsafe extern "C" fn(*const std::ffi::c_void) = std::mem::transmute(entry.bind_fn);
149            bind(ptr);
150        }
151    }
152}
153
154pub use yog_command::CommandContext;
155pub use yog_core::{BlockPos, Server};
156pub use yog_event::{
157    AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
158    ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
159    EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent,
160    ItemPickupEvent, KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent,
161    PlayerLeaveEvent, PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent,
162    UseBlockEvent, UseItemEvent,
163};
164pub use yog_entity::Entity;
165pub use yog_network::{Packet, PacketEvent, PacketField};
166#[doc(inline)]
167pub use yog_network::packet;
168pub use yog_player::Player;
169pub use yog_registry::{BlockDef, FoodDef, FurnaceRecipe, ItemDef, ShapedRecipe, ShapelessRecipe, BookRecipe, ItemModifier, AdvancementReward, StartupGrant};
170pub use yog_config::Config;
171pub use yog_storage::{Storage, StorageScope, Value};
172pub use yog_world::World;
173pub use yog_book::{Book, BookCategory, BookEntry, BookPage, BookMacro, BookRegistry};
174pub use yog_book::{BookRenderer, BookFontRegistry};
175pub use yog_book::{text_page, text_page_titled, spotlight_page, crafting_page, smelting_page, image_page, entity_page, relations_page, pattern_page};
176pub use yog_ui::{UiRoot, LayoutNode, Rect, widget, Align, FlexDir, Dock, FocusStyle};
177pub use yog_inventory::{InventoryDef, SlotLayout};
178
179/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
180pub use yog_logging::{error, info, warn};
181
182/// Core types and handles.
183pub mod core {
184    pub use yog_core::*;
185}
186
187/// Events and the subscription registry.
188pub mod event {
189    pub use yog_event::*;
190}
191
192/// World access (block get/set, dimensions).
193pub mod world {
194    pub use yog_world::*;
195}
196
197/// Entity access (teleport, position, health, ... by UUID).
198pub mod entity {
199    pub use yog_entity::*;
200}
201
202/// Player access (give item, teleport).
203pub mod player {
204    pub use yog_player::*;
205}
206
207/// Content registration (custom items / blocks / food).
208pub mod content {
209    pub use yog_registry::*;
210}
211
212/// Networking (raw-byte packets over channels).
213pub mod network {
214    pub use yog_network::*;
215}
216
217/// Commands.
218pub mod command {
219    pub use yog_command::*;
220}
221
222/// Persistent key-value storage for mod data.
223pub mod storage {
224    pub use yog_storage::*;
225}
226
227/// Mod configuration (typed key/value files).
228pub mod config {
229    pub use yog_config::*;
230}
231
232/// In-game book/documentation system (Patchouli-like).
233pub mod book {
234    pub use yog_book::*;
235}
236
237/// UI framework — flexbox layout + widgets on top of yog-gfx.
238pub mod ui {
239    pub use yog_ui::*;
240}
241
242/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
243/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
244pub mod inventory {
245    pub use yog_inventory::*;
246}