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