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