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