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