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
8pub mod client;
9mod interop;
10mod lang;
11mod registry;
12
13pub use interop::Interop;
14pub use lang::{format_translation, tr, tr_args, translation};
15pub use registry::{
16    close_ui, installed_mods, open_ui, server, CServer, Mod, ModInfo, Registry, UiHandled,
17};
18pub use yog_gfx::{
19    core as gfx_core, draw2d as gfx_draw2d, gl as gfx_gl, image as gfx_image, GfxContext,
20};
21
22/// Stable C ABI — re-exported so mods don't need a direct `yog-abi` dependency.
23pub use yog_abi::{YogApi, ABI_VERSION};
24
25/// Inter-mod communication proc-macros.
26pub use yog_interop::{import, yog_export};
27
28/// rkyv — zero-copy serialization for interop. Re-exported so generated
29/// code and mods can use `rkyv::Archive`, `rkyv::Serialize`, `rkyv::Deserialize`
30/// without a direct dependency.
31pub use rkyv;
32
33/// Re-exported so `#[yog_export]`-generated code can emit `#[::yog_api::ctor::ctor(unsafe)]`
34/// without mods needing a direct `ctor` dependency. Constructors run when the
35/// mod's `cdylib` is loaded — guaranteed to complete before the runtime calls
36/// `yog_mod_register`, so the export registry is fully populated by then.
37pub use ctor;
38
39#[doc(hidden)]
40pub use std::os::raw::c_void as __c_void;
41#[doc(hidden)]
42pub use yog_abi::{YogGfxApi as __YogGfxApi, YogStr as __YogStr};
43
44/// Export a [`Mod`] as a dynamically loadable Yog mod.
45///
46/// Generates the two C-ABI entry points the runtime looks up:
47/// - `yog_abi_version() -> u32`  — version check before loading
48/// - `yog_mod_register(*const YogApi, *const c_char)` — registration entry point,
49///   receives the mod's `id` from its manifest
50///
51/// Put this once at the crate root of a `cdylib` mod:
52///
53/// ```ignore
54/// yog_api::export_mod!(MyMod);
55/// ```
56#[macro_export]
57macro_rules! export_mod {
58    ($mod_ty:ty) => {
59        #[no_mangle]
60        pub extern "C" fn yog_abi_version() -> u32 {
61            $crate::ABI_VERSION
62        }
63
64        #[no_mangle]
65        pub unsafe extern "C" fn yog_mod_register(
66            api: *const $crate::YogApi,
67            mod_id_ptr: *const ::std::os::raw::c_char,
68        ) {
69            // Parse mod_id from the C string passed by the runtime.
70            let mod_id: &str = if mod_id_ptr.is_null() {
71                "unknown"
72            } else {
73                match ::std::ffi::CStr::from_ptr(mod_id_ptr).to_str() {
74                    Ok(s) => s,
75                    Err(_) => "unknown",
76                }
77            };
78            // Store for interop use (yog_api::interop::current_mod_id()).
79            $crate::__set_current_mod_id(mod_id);
80            // Every mod is handed the same global `YogApi` table, so any
81            // mod's own pointer works for reconstructing a `Registry` inside
82            // an exported function later — even when called from a thread
83            // that never ran `yog_mod_register` itself.
84            $crate::__set_current_api_ptr(api as usize);
85
86            // Catch panics so they never unwind across this `extern "C"` boundary
87            // back into the runtime (which would be undefined behaviour).
88            let outcome = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
89                // SAFETY: the runtime passes a valid YogApi pointer, verified via
90                // yog_abi_version() and abi_version/size checks before this call.
91                let mut registry = unsafe { $crate::Registry::from_raw(api) };
92                <$mod_ty as $crate::Mod>::register(&mut registry);
93
94                // Blocks and items of `content/**/*.yblock|*.yitem`
95                // (`yog_api::content!()`, self-registered by `ctor`).
96                let content: ::std::vec::Vec<fn(&mut $crate::Registry)> =
97                    $crate::__yog_content_registrars()
98                        .lock()
99                        .unwrap_or_else(|e| e.into_inner())
100                        .clone();
101                for register in content {
102                    register(&mut registry);
103                }
104
105                // Auto-register all #[yog_export] functions (populated by
106                // per-function `ctor` constructors at cdylib load time).
107                for entry in $crate::__yog_export_registry().lock().unwrap().iter() {
108                    registry
109                        .interop()
110                        .export(entry.name, entry.ptr as *const ::std::os::raw::c_void);
111                }
112
113                // Resolve pending imports (may partially succeed — runtime
114                // does a final pass after all mods loaded).
115                $crate::__yog_resolve_pending_imports(&registry);
116            }));
117            if outcome.is_err() {
118                $crate::error!(
119                    "mod {} panicked during register",
120                    ::core::stringify!($mod_ty)
121                );
122            }
123        }
124
125        /// Called by the runtime after ALL mods are loaded — resolves any
126        /// imports that weren't satisfied during initial registration.
127        #[no_mangle]
128        pub unsafe extern "C" fn __yog_resolve_imports_final(api: *const $crate::YogApi) {
129            let registry = unsafe { $crate::Registry::from_raw(api) };
130            $crate::__yog_resolve_pending_imports(&registry);
131        }
132
133        /// Yog-IDLE's screen designer: the version of the protocol below.
134        #[no_mangle]
135        pub extern "C" fn yog_ui_designer_abi() -> u32 {
136            $crate::ui::component::designer::ABI
137        }
138
139        /// Yog-IDLE's screen designer: lists this mod's custom components
140        /// and draws them (see `yog_ui::component::designer`). Never called
141        /// in game — only by the designer host, which loads the mod's
142        /// library built for the developer's machine.
143        #[no_mangle]
144        pub unsafe extern "C" fn yog_ui_designer(
145            request: $crate::__YogStr,
146            gfx: *const $crate::__YogGfxApi,
147            user_data: *mut $crate::__c_void,
148            write: $crate::ui::component::designer::WriteFn,
149        ) -> bool {
150            // SAFETY: forwarded under the same contract.
151            unsafe { $crate::ui::component::designer::call(request, gfx, user_data, write) }
152        }
153    };
154}
155
156/// Registers `.yui` components (types implementing
157/// [`ui::component::Component`]) when the mod's library loads, so screens
158/// can use `<Name …/>` and Yog-IDLE's designer host can draw them. The code
159/// `yog build` generates from a `<Component>` `.yui` calls this — mods
160/// don't write it themselves.
161#[macro_export]
162macro_rules! ui_component {
163    ($($ty:ty),+ $(,)?) => {
164        const _: () = {
165            #[$crate::ctor::ctor(unsafe, crate_path = $crate::ctor)]
166            fn __yog_register_ui_components() {
167                $( $crate::ui::component::register::<$ty>(); )+
168            }
169        };
170    };
171}
172
173/// Compiles in the mod's component `.yui` files that have no code-behind
174/// `.rs` (those with one are pulled in by its `ui_include!`) — call it once,
175/// in `src/lib.rs`:
176///
177/// ```ignore
178/// yog_api::ui_components!();
179/// ```
180///
181/// `yog build`, `yog check` and `yog ui gen` write the list to
182/// `.yog-build/generated/components.g.rs` (a module per component).
183#[macro_export]
184macro_rules! ui_components {
185    () => {
186        include!(concat!(
187            env!("CARGO_MANIFEST_DIR"),
188            "/generated/components.g.rs"
189        ));
190    };
191}
192
193/// Internal: set by `export_mod!` before calling `Mod::register`.
194/// Used by `yog_api::interop::current_mod_id()` so `Interop::export` knows
195/// which mod is calling.
196#[doc(hidden)]
197pub fn __set_current_mod_id(id: &str) {
198    CURRENT_MOD_ID.with(|cell| cell.replace(Some(id.to_string())));
199}
200
201/// Internal: the current mod's id, set during `yog_mod_register`.
202#[doc(hidden)]
203pub fn __current_mod_id() -> Option<String> {
204    CURRENT_MOD_ID.with(|cell| cell.borrow().clone())
205}
206
207/// Internal: the global `YogApi` pointer, set by every mod's `yog_mod_register`
208/// (it's the same singleton table for the whole process). Process-wide (not
209/// thread-local) because `#[yog_export]`-generated wrappers read it whenever
210/// an exported function is *called*, which may happen on any thread, long
211/// after the loading thread's `yog_mod_register` returned.
212#[doc(hidden)]
213pub fn __set_current_api_ptr(ptr: usize) {
214    API_PTR.store(ptr, std::sync::atomic::Ordering::Release);
215}
216
217#[doc(hidden)]
218pub fn __current_api_ptr() -> usize {
219    API_PTR.load(std::sync::atomic::Ordering::Acquire)
220}
221
222static API_PTR: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
223
224std::thread_local! {
225    static CURRENT_MOD_ID: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
226}
227
228// ── Auto-export registry ─────────────────────────────────────────────────
229
230#[doc(hidden)]
231pub struct YogExportEntry {
232    pub name: &'static str,
233    pub ptr: usize,
234}
235
236#[doc(hidden)]
237pub struct YogImportEntry {
238    pub mod_id: &'static str,
239    pub symbol: &'static str,
240    pub bind_fn: usize,
241}
242
243#[doc(hidden)]
244pub fn __yog_export_registry() -> &'static std::sync::Mutex<Vec<YogExportEntry>> {
245    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogExportEntry>>> =
246        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
247    &REG
248}
249
250/// The `register` functions of `yog_api::content!()` — run by
251/// `export_mod!` after `Mod::register`.
252#[doc(hidden)]
253pub fn __yog_content_registrars() -> &'static std::sync::Mutex<Vec<fn(&mut Registry)>> {
254    static REG: std::sync::Mutex<Vec<fn(&mut Registry)>> = std::sync::Mutex::new(Vec::new());
255    &REG
256}
257
258#[doc(hidden)]
259pub fn __yog_import_registry() -> &'static std::sync::Mutex<Vec<YogImportEntry>> {
260    static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogImportEntry>>> =
261        std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
262    &REG
263}
264
265/// Called by `export_mod!` after registration. Iterates pending imports and
266/// resolves them via the interop table. Also exported as `#[no_mangle]` so the
267/// runtime can call it after all mods are loaded (final resolution pass).
268#[doc(hidden)]
269pub unsafe fn __yog_resolve_pending_imports(registry: &crate::Registry) {
270    let interop = registry.interop();
271    for entry in __yog_import_registry().lock().unwrap().iter() {
272        if let Some(ptr) = interop.import_raw(entry.mod_id, entry.symbol) {
273            let bind: unsafe extern "C" fn(*const std::ffi::c_void) =
274                std::mem::transmute(entry.bind_fn);
275            bind(ptr);
276        }
277    }
278}
279
280pub use yog_book::{
281    crafting_page, entity_page, image_page, pattern_page, relations_page, smelting_page,
282    spotlight_page, text_page, text_page_titled,
283};
284pub use yog_book::{Book, BookCategory, BookEntry, BookMacro, BookPage, BookRegistry};
285pub use yog_book::{BookFontRegistry, BookRenderer};
286pub use yog_command::CommandContext;
287pub use yog_config::Config;
288pub use yog_core::{BlockPos, Server};
289pub use yog_dimensions::{ChunkWriter, YogDimensionDef, YogDimensionTypeDef};
290pub use yog_entity::Entity;
291pub use yog_event::{
292    AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
293    ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
294    EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent, ItemPickupEvent,
295    KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent, PlayerLeaveEvent,
296    PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent, UseBlockEvent,
297    UseItemEvent,
298};
299pub use yog_inventory::{InventoryDef, SlotLayout};
300#[doc(inline)]
301pub use yog_network::packet;
302pub use yog_network::{Packet, PacketEvent, PacketField};
303pub use yog_player::Player;
304pub use yog_registry::{
305    AdvancementReward, BlockDef, BookRecipe, FoodDef, FurnaceRecipe, ItemDef, ItemModifier,
306    ShapedRecipe, ShapelessRecipe, StartupGrant,
307};
308pub use yog_storage::{Storage, StorageScope, Value};
309pub use yog_ui::{
310    widget, Align, Dock, FlexDir, FocusStyle, LayoutNode, Rect, Stretch, UiRoot, Units,
311};
312pub use yog_world::World;
313
314/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
315pub use yog_logging::{error, info, warn};
316
317/// Core types and handles.
318pub mod core {
319    pub use yog_core::*;
320}
321
322/// Events and the subscription registry.
323pub mod event {
324    pub use yog_event::*;
325}
326
327/// World access (block get/set, dimensions).
328pub mod world {
329    pub use yog_world::*;
330}
331
332/// Entity access (teleport, position, health, ... by UUID).
333pub mod entity {
334    pub use yog_entity::*;
335}
336
337/// Player access (give item, teleport).
338pub mod player {
339    pub use yog_player::*;
340}
341
342/// Content registration (custom items / blocks / food).
343pub mod content {
344    pub use yog_registry::*;
345}
346
347/// Networking (raw-byte packets over channels).
348pub mod network {
349    pub use yog_network::*;
350}
351
352/// Commands.
353pub mod command {
354    pub use yog_command::*;
355}
356
357/// Custom dimensions — dimension-type definitions and runtime chunk generation.
358pub mod dimension {
359    pub use yog_dimensions::*;
360}
361
362/// Persistent key-value storage for mod data.
363pub mod storage {
364    pub use yog_storage::*;
365}
366
367/// Mod configuration (typed key/value files).
368pub mod config {
369    pub use yog_config::*;
370}
371
372/// In-game book/documentation system (Patchouli-like).
373pub mod book {
374    pub use yog_book::*;
375}
376
377/// UI framework — flexbox layout + widgets on top of yog-gfx.
378pub mod ui {
379    pub use yog_ui::*;
380}
381
382/// Pulls a `.yui` screen's generated code into the current module — the
383/// code-behind's half of `src/<name>.yui`:
384///
385/// ```ignore
386/// // src/mod_list.rs, next to src/mod_list.yui
387/// yog_api::ui_include!("mod_list");
388/// ```
389///
390/// `<name>` is the `.yui` file's path under `src/` without the extension
391/// (`"screens/options"` for `src/screens/options.yui`). `yog build`,
392/// `yog check` and `yog ui gen` write the code to
393/// `.yog-build/generated/<name>.g.rs` (the build crate's own directory,
394/// `CARGO_MANIFEST_DIR`), so it's never committed and never stale.
395#[macro_export]
396macro_rules! ui_include {
397    ($name:literal) => {
398        include!(concat!(
399            env!("CARGO_MANIFEST_DIR"),
400            "/generated/",
401            $name,
402            ".g.rs"
403        ));
404    };
405}
406
407/// Pulls the mod's `.yres` resources in as module `res` — one type per
408/// resource set, with an accessor per entry:
409///
410/// ```ignore
411/// // src/lib.rs
412/// yog_api::resources!();
413///
414/// use crate::res::{Icons, Strings};
415/// let title = Strings::screen_title();        // the text in the player's language
416/// let count = Strings::mods_count(58);        // "{count} mods" → "58 mods"
417/// let logo = Icons::logo();                   // an image source, "mymod:images/logo"
418/// ```
419///
420/// `yog build`, `yog check` and `yog res check` generate one
421/// `.yog-build/generated/resources/<Set>.g.rs` per `resources/**/<Set>.yres`
422/// plus `.yog-build/generated/resources.g.rs` including them all (see
423/// Yog-IDLE's `docs/yres.md`); never committed, never stale.
424#[macro_export]
425macro_rules! resources {
426    () => {
427        pub mod res {
428            include!(concat!(
429                env!("CARGO_MANIFEST_DIR"),
430                "/generated/resources.g.rs"
431            ));
432        }
433    };
434}
435
436/// Pulls the mod's blocks and items — `content/**/*.yblock` and `*.yitem`,
437/// made in Yog-IDLE's model editor — in as module `content`, and registers
438/// them (every file without `"register": "manual"`) right after
439/// `Mod::register`:
440///
441/// ```ignore
442/// // src/lib.rs — that's all it takes
443/// yog_api::content!();
444///
445/// // Ids and definitions, for code that needs them:
446/// use crate::content::{Blocks, Items};
447/// let id: &str = Blocks::RUBY_ORE;                 // "mymod:ruby_ore"
448/// registry.register_item(Items::ruby().max_stack(16)); // a "manual" one, tweaked
449/// ```
450///
451/// `yog build`, `yog check` and `yog content check` generate
452/// `.yog-build/generated/yog-content.g.rs` — never committed, never stale.
453/// The models, blockstates, loot tables, tags and lang entries go into the
454/// `.yog` as assets. See Yog-IDLE's `docs/blocks-items.md`.
455#[macro_export]
456macro_rules! content {
457    () => {
458        pub mod content {
459            include!(concat!(
460                env!("CARGO_MANIFEST_DIR"),
461                "/generated/yog-content.g.rs"
462            ));
463        }
464    };
465}
466
467/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
468/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
469pub mod inventory {
470    pub use yog_inventory::*;
471}