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