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