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