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 (via linkme distributed slice).
75                for entry in $crate::YOG_EXPORTS.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 (used by #[yog_export] proc-macro) ──────────────
119
120/// An entry in the distributed export slice. Each `#[yog_export]` function
121/// contributes one entry via `#[linkme::distributed_slice]`.
122#[doc(hidden)]
123pub struct YogExportEntry {
124    pub name: &'static str,
125    pub ptr: usize,
126}
127
128/// Distributed slice — populated by `#[yog_export]` across all modules.
129#[doc(hidden)]
130#[linkme::distributed_slice]
131pub static YOG_EXPORTS: [YogExportEntry] = [..];
132
133/// An entry in the distributed pending-imports slice.
134#[doc(hidden)]
135pub struct YogImportEntry {
136    pub mod_id: &'static str,
137    pub symbol: &'static str,
138    pub bind_fn: usize,
139}
140
141/// Distributed slice — populated by `import!` across all modules.
142#[doc(hidden)]
143#[linkme::distributed_slice]
144pub static YOG_IMPORTS: [YogImportEntry] = [..];
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_IMPORTS.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}