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::{core as gfx_core, draw2d as gfx_draw2d, gl as gfx_gl, GfxContext};
14
15/// Stable C ABI — re-exported so mods don't need a direct `yog-abi` dependency.
16pub use yog_abi::{YogApi, ABI_VERSION};
17
18/// Inter-mod communication proc-macros.
19pub use yog_interop::{import, yog_export};
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            // Every mod is handed the same global `YogApi` table, so any
72            // mod's own pointer works for reconstructing a `Registry` inside
73            // an exported function later — even when called from a thread
74            // that never ran `yog_mod_register` itself.
75            $crate::__set_current_api_ptr(api as usize);
76
77            // Catch panics so they never unwind across this `extern "C"` boundary
78            // back into the runtime (which would be undefined behaviour).
79            let outcome = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
80                // SAFETY: the runtime passes a valid YogApi pointer, verified via
81                // yog_abi_version() and abi_version/size checks before this call.
82                let mut registry = unsafe { $crate::Registry::from_raw(api) };
83                <$mod_ty as $crate::Mod>::register(&mut registry);
84
85                // Auto-register all #[yog_export] functions (populated by
86                // per-function `ctor` constructors at cdylib load time).
87                for entry in $crate::__yog_export_registry().lock().unwrap().iter() {
88                    registry
89                        .interop()
90                        .export(entry.name, entry.ptr as *const ::std::os::raw::c_void);
91                }
92
93                // Resolve pending imports (may partially succeed — runtime
94                // does a final pass after all mods loaded).
95                $crate::__yog_resolve_pending_imports(&registry);
96            }));
97            if outcome.is_err() {
98                $crate::error!(
99                    "mod {} panicked during register",
100                    ::core::stringify!($mod_ty)
101                );
102            }
103        }
104
105        /// Called by the runtime after ALL mods are loaded — resolves any
106        /// imports that weren't satisfied during initial registration.
107        #[no_mangle]
108        pub unsafe extern "C" fn __yog_resolve_imports_final(api: *const $crate::YogApi) {
109            let registry = unsafe { $crate::Registry::from_raw(api) };
110            $crate::__yog_resolve_pending_imports(&registry);
111        }
112    };
113}
114
115/// Internal: set by `export_mod!` before calling `Mod::register`.
116/// Used by `yog_api::interop::current_mod_id()` so `Interop::export` knows
117/// which mod is calling.
118#[doc(hidden)]
119pub fn __set_current_mod_id(id: &str) {
120    CURRENT_MOD_ID.with(|cell| cell.replace(Some(id.to_string())));
121}
122
123/// Internal: the current mod's id, set during `yog_mod_register`.
124#[doc(hidden)]
125pub fn __current_mod_id() -> Option<String> {
126    CURRENT_MOD_ID.with(|cell| cell.borrow().clone())
127}
128
129/// Internal: the global `YogApi` pointer, set by every mod's `yog_mod_register`
130/// (it's the same singleton table for the whole process). Process-wide (not
131/// thread-local) because `#[yog_export]`-generated wrappers read it whenever
132/// an exported function is *called*, which may happen on any thread, long
133/// after the loading thread's `yog_mod_register` returned.
134#[doc(hidden)]
135pub fn __set_current_api_ptr(ptr: usize) {
136    API_PTR.store(ptr, std::sync::atomic::Ordering::Release);
137}
138
139#[doc(hidden)]
140pub fn __current_api_ptr() -> usize {
141    API_PTR.load(std::sync::atomic::Ordering::Acquire)
142}
143
144static API_PTR: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
145
146std::thread_local! {
147    static CURRENT_MOD_ID: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
148}
149
150// ── Auto-export registry ─────────────────────────────────────────────────
151
152#[doc(hidden)]
153pub struct YogExportEntry {
154    pub name: &'static str,
155    pub ptr: usize,
156}
157
158#[doc(hidden)]
159pub struct YogImportEntry {
160    pub mod_id: &'static str,
161    pub symbol: &'static str,
162    pub bind_fn: usize,
163}
164
165#[doc(hidden)]
166pub fn __yog_export_registry() -> &'static std::sync::Mutex<Vec<YogExportEntry>> {
167    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogExportEntry>>> =
168        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
169    &REG
170}
171
172#[doc(hidden)]
173pub fn __yog_import_registry() -> &'static std::sync::Mutex<Vec<YogImportEntry>> {
174    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogImportEntry>>> =
175        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
176    &REG
177}
178
179/// Called by `export_mod!` after registration. Iterates pending imports and
180/// resolves them via the interop table. Also exported as `#[no_mangle]` so the
181/// runtime can call it after all mods are loaded (final resolution pass).
182#[doc(hidden)]
183pub unsafe fn __yog_resolve_pending_imports(registry: &crate::Registry) {
184    let interop = registry.interop();
185    for entry in __yog_import_registry().lock().unwrap().iter() {
186        if let Some(ptr) = interop.import_raw(entry.mod_id, entry.symbol) {
187            let bind: unsafe extern "C" fn(*const std::ffi::c_void) =
188                std::mem::transmute(entry.bind_fn);
189            bind(ptr);
190        }
191    }
192}
193
194pub use yog_book::{
195    crafting_page, entity_page, image_page, pattern_page, relations_page, smelting_page,
196    spotlight_page, text_page, text_page_titled,
197};
198pub use yog_book::{Book, BookCategory, BookEntry, BookMacro, BookPage, BookRegistry};
199pub use yog_book::{BookFontRegistry, BookRenderer};
200pub use yog_command::CommandContext;
201pub use yog_config::Config;
202pub use yog_core::{BlockPos, Server};
203pub use yog_dimensions::{ChunkWriter, YogDimensionDef, YogDimensionTypeDef};
204pub use yog_entity::Entity;
205pub use yog_event::{
206    AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
207    ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
208    EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent, ItemPickupEvent,
209    KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent, PlayerLeaveEvent,
210    PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent, UseBlockEvent,
211    UseItemEvent,
212};
213pub use yog_inventory::{InventoryDef, SlotLayout};
214#[doc(inline)]
215pub use yog_network::packet;
216pub use yog_network::{Packet, PacketEvent, PacketField};
217pub use yog_player::Player;
218pub use yog_registry::{
219    AdvancementReward, BlockDef, BookRecipe, FoodDef, FurnaceRecipe, ItemDef, ItemModifier,
220    ShapedRecipe, ShapelessRecipe, StartupGrant,
221};
222pub use yog_storage::{Storage, StorageScope, Value};
223pub use yog_ui::{widget, Align, Dock, FlexDir, FocusStyle, LayoutNode, Rect, UiRoot, Units};
224pub use yog_world::World;
225
226/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
227pub use yog_logging::{error, info, warn};
228
229/// Core types and handles.
230pub mod core {
231    pub use yog_core::*;
232}
233
234/// Events and the subscription registry.
235pub mod event {
236    pub use yog_event::*;
237}
238
239/// World access (block get/set, dimensions).
240pub mod world {
241    pub use yog_world::*;
242}
243
244/// Entity access (teleport, position, health, ... by UUID).
245pub mod entity {
246    pub use yog_entity::*;
247}
248
249/// Player access (give item, teleport).
250pub mod player {
251    pub use yog_player::*;
252}
253
254/// Content registration (custom items / blocks / food).
255pub mod content {
256    pub use yog_registry::*;
257}
258
259/// Networking (raw-byte packets over channels).
260pub mod network {
261    pub use yog_network::*;
262}
263
264/// Commands.
265pub mod command {
266    pub use yog_command::*;
267}
268
269/// Custom dimensions — dimension-type definitions and runtime chunk generation.
270pub mod dimension {
271    pub use yog_dimensions::*;
272}
273
274/// Persistent key-value storage for mod data.
275pub mod storage {
276    pub use yog_storage::*;
277}
278
279/// Mod configuration (typed key/value files).
280pub mod config {
281    pub use yog_config::*;
282}
283
284/// In-game book/documentation system (Patchouli-like).
285pub mod book {
286    pub use yog_book::*;
287}
288
289/// UI framework — flexbox layout + widgets on top of yog-gfx.
290pub mod ui {
291    pub use yog_ui::*;
292}
293
294/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
295/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
296pub mod inventory {
297    pub use yog_inventory::*;
298}