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