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