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;
10mod bus;
11pub mod client;
12mod draw;
13pub mod entities;
14pub mod java;
15pub mod loot;
16pub mod mirror;
17// The generated code names `::yog_api`, as in a mod (the host's mirrors are made here).
18extern crate self as yog_api;
19pub mod vanilla;
20pub use bus::{EventCx, Flow, IntoFlow, emit};
21pub use service::call;
22mod interop;
23mod lang;
24mod registry;
25mod scheduler;
26mod service;
27
28#[doc(hidden)]
29pub use interop::{__yog_export_registry, __yog_serve, YogExportEntry};
30pub use interop::{call_export, export_address};
31pub use lang::{format_translation, tag_name, tr, tr_args, translation};
32pub use registry::{Mod, ModInfo, Registry, UiHandled, close_ui, installed_mods, open_ui};
33pub use yog_gfx::{
34    GfxContext, core as gfx_core, draw2d as gfx_draw2d, gl as gfx_gl, image as gfx_image,
35};
36
37/// Stable C ABI — re-exported so mods don't need a direct `yog-abi` dependency.
38pub use yog_abi::{ABI_VERSION, YogApi};
39#[doc(hidden)]
40pub use yog_abi::{YogBytes, YogReply, YogStatus};
41
42/// Yog Data — what content definitions and book pages' `data`
43/// (`Registry::on_book_page`) are; re-exported for mods.
44pub use yog_data;
45/// `#[derive(Schema)]`: a type read and written as Yog Data (stores, packets, frames).
46pub use yog_data::Schema;
47/// The names Yog shares — kinds, namespaces, lang keys — for a mod to use as the loader does.
48pub use yog_ids;
49/// The sync primitives Yog code uses (locks that outlive a panic) — a mod's too.
50pub use yog_sync as sync;
51
52pub use java::mod_loaded;
53
54/// Inter-mod communication proc-macros.
55pub use yog_interop::{bench, import, yog_export, yog_mirror};
56
57/// Re-exported so `#[yog_export]`-generated code can emit `#[::yog_api::ctor::ctor(unsafe)]`
58/// without mods needing a direct `ctor` dependency. Constructors run when the
59/// mod's `cdylib` is loaded — guaranteed to complete before the runtime calls
60/// `yog_mod_register`, so the export registry is fully populated by then.
61pub use ctor;
62
63#[doc(hidden)]
64pub use std::os::raw::c_void as __c_void;
65#[doc(hidden)]
66pub use yog_abi::{YogGfxApi as __YogGfxApi, YogStr as __YogStr};
67
68/// Export a [`Mod`] as a dynamically loadable Yog mod.
69///
70/// Generates the two C-ABI entry points the runtime looks up:
71/// - `yog_abi_version() -> u32`  — version check before loading
72/// - `yog_mod_register(*const YogApi, *const c_char)` — registration entry point,
73///   receives the mod's `id` from its manifest
74///
75/// Put this once at the crate root of a `cdylib` mod:
76///
77/// ```ignore
78/// yog_api::export_mod!(MyMod);
79/// ```
80#[macro_export]
81macro_rules! export_mod {
82    ($mod_ty:ty) => {
83        #[unsafe(no_mangle)]
84        pub extern "C" fn yog_abi_version() -> u32 {
85            $crate::ABI_VERSION
86        }
87
88        #[unsafe(no_mangle)]
89        pub unsafe extern "C" fn yog_mod_register(
90            api: *const $crate::YogApi,
91            mod_id_ptr: *const ::std::os::raw::c_char,
92        ) {
93            // Parse mod_id from the C string passed by the runtime.
94            let mod_id: &str = if mod_id_ptr.is_null() {
95                "unknown"
96            } else {
97                // SAFETY: a non-null id is a NUL-terminated string from the runtime.
98                match unsafe { ::std::ffi::CStr::from_ptr(mod_id_ptr) }.to_str() {
99                    Ok(s) => s,
100                    Err(_) => "unknown",
101                }
102            };
103            // Store for interop use (yog_api::interop::current_mod_id()).
104            $crate::__set_current_mod_id(mod_id);
105            // Every mod is handed the same global `YogApi` table, so any
106            // mod's own pointer works for reconstructing a `Registry` inside
107            // an exported function later — even when called from a thread
108            // that never ran `yog_mod_register` itself.
109            $crate::__set_current_api_ptr(api as usize);
110
111            // Catch panics so they never unwind across this `extern "C"` boundary
112            // back into the runtime (which would be undefined behaviour).
113            let outcome = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
114                // SAFETY: the runtime passes a valid YogApi pointer, verified via
115                // yog_abi_version() and abi_version/size checks before this call.
116                let mut registry = unsafe { $crate::Registry::from_raw(api) };
117                // The world's folder known before the mod's own handlers run.
118                $crate::storage::__know_world(&mut registry);
119                <$mod_ty as $crate::Mod>::register(&mut registry);
120
121                // Blocks and items of `content/**/*.yblock|*.yitem`
122                // (`yog_api::content!()`, self-registered by `ctor`).
123                let content: ::std::vec::Vec<fn(&mut $crate::Registry)> =
124                    $crate::__yog_content_registrars().lock().clone();
125                for register in content {
126                    register(&mut registry);
127                }
128
129                // Every #[yog_export] function (registered by its `ctor`
130                // when the library loaded) provides `mod_id:name`.
131                for entry in $crate::__yog_export_registry().lock().iter() {
132                    registry.provide_export(mod_id, entry);
133                }
134
135                // What it keeps, saved with the world (after the mod's own handlers).
136                $crate::storage::__save_with_world(&mut registry);
137            }));
138            if outcome.is_err() {
139                $crate::error!(
140                    "mod {} panicked during register",
141                    ::core::stringify!($mod_ty)
142                );
143            }
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::YogBytes,
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    let _ = MOD_ID.set(id.to_owned());
213}
214
215/// This mod's id (its manifest's), once the runtime has loaded it.
216pub fn mod_id() -> &'static str {
217    MOD_ID.get().map_or("", String::as_str)
218}
219
220/// This mod's id: each mod's library has its own copy of this crate.
221static MOD_ID: yog_sync::OnceLock<String> = yog_sync::OnceLock::new();
222
223/// Internal: the current mod's id, set during `yog_mod_register`.
224#[doc(hidden)]
225pub fn __current_mod_id() -> Option<String> {
226    CURRENT_MOD_ID.with(|cell| cell.borrow().clone())
227}
228
229/// Internal: the global `YogApi` pointer, set by every mod's `yog_mod_register`
230/// (it's the same singleton table for the whole process). Process-wide (not
231/// thread-local) because `#[yog_export]`-generated wrappers read it whenever
232/// an exported function is *called*, which may happen on any thread, long
233/// after the loading thread's `yog_mod_register` returned.
234#[doc(hidden)]
235pub fn __set_current_api_ptr(ptr: usize) {
236    API_PTR.store(ptr, yog_sync::atomic::Ordering::Release);
237}
238
239/// The `register` functions of `yog_api::content!()` — the content of
240/// `content/**` the build generates — run by `export_mod!` after
241/// `Mod::register`.
242#[doc(hidden)]
243pub fn __yog_content_registrars() -> &'static yog_sync::Mutex<Vec<fn(&mut Registry)>> {
244    static REG: yog_sync::Mutex<Vec<fn(&mut Registry)>> = yog_sync::Mutex::new(Vec::new());
245    &REG
246}
247
248#[doc(hidden)]
249pub fn __current_api_ptr() -> usize {
250    API_PTR.load(yog_sync::atomic::Ordering::Acquire)
251}
252
253static API_PTR: yog_sync::atomic::AtomicUsize = yog_sync::atomic::AtomicUsize::new(0);
254
255std::thread_local! {
256    static CURRENT_MOD_ID: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
257}
258
259pub use yog_book::BookRenderer;
260pub use yog_command::CommandContext;
261pub use yog_config::Config;
262pub use yog_core::{
263    BlockHit, BlockInfo, BlockPos, EntityInfo, Face, PlacedBlock, PlayerInfo, Point, Rotation,
264    ScreenSlot, SlotStack, Stack, entity_flags, split_state,
265};
266pub use yog_dimensions::{GeneratedChunk, YogDimensionDef, YogDimensionTypeDef};
267pub use yog_event::{Entity, Event, EventPhase, Message, Player, Service, World};
268/// A packet the server or the client received (`on_packet` / `on_client_packet`).
269pub use yog_event::{client::net::Packet as ClientPacketEvent, server::net::Packet as PacketEvent};
270pub use yog_formats::book::{Book, BookCategory, BookEntry, BookPage};
271pub use yog_inventory::{InventoryDef, SlotLayout};
272pub use yog_network::Packet;
273pub use yog_registry::{
274    ArmorDefense, ArmorMaterial, ArmorPiece, ArmorSlot, BlockDef, BlockProperty, Cooking,
275    EntityDef, EntityKind, Facing, FoodDef, ItemDef, Placement, Recipe, RecipeKind, SoundGroup,
276    StartupGrant, ToolItem, ToolMaterial,
277};
278pub use yog_ui::{
279    Align, Dock, FlexDir, FocusStyle, LayoutNode, Rect, Stretch, UiRoot, Units, widget,
280};
281
282/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
283pub use yog_logging::{error, info, warn};
284
285/// Core types and handles.
286pub mod core {
287    pub use yog_core::*;
288}
289
290/// Events by plane — `event::player::session::Join`, `event::block::Break`, … —
291/// subscribed with `Registry::on` / `before` / `after`.
292pub mod event {
293    pub use yog_event::*;
294}
295
296/// The bus's domains: each one's actions as functions
297/// (`world::get_block(dim, pos)`, `entity::position(uuid)`,
298/// `player::give_item(name, item, count)`) beside its events.
299pub use yog_event::{
300    block, bossbar, chat, entity, game, item, net, particle, player, scoreboard, sound, tag, world,
301};
302
303/// Content registration (custom items / blocks / food).
304pub mod content {
305    pub use yog_registry::*;
306}
307
308/// Networking (raw-byte packets over channels).
309pub mod network {
310    pub use yog_network::*;
311}
312
313/// Commands.
314pub mod command {
315    pub use yog_command::*;
316}
317
318/// Custom dimensions — dimension-type definitions and runtime chunk generation.
319pub mod dimension {
320    pub use yog_dimensions::*;
321}
322
323/// Where the mod keeps its data (typed stores by scope, values kept with the world).
324pub mod storage;
325
326/// Mod configuration (typed key/value files).
327pub mod config {
328    pub use yog_config::*;
329}
330
331/// Guide books: the `.ybook` format and its renderer.
332pub mod book {
333    pub use yog_book::*;
334    pub use yog_formats::book::*;
335}
336
337/// UI framework — flexbox layout + widgets on top of yog-gfx.
338pub mod ui {
339    pub use yog_ui::*;
340}
341
342/// Pulls a `.yui` screen's generated code into the current module — the
343/// code-behind's half of `src/<name>.yui`:
344///
345/// ```ignore
346/// // src/mod_list.rs, next to src/mod_list.yui
347/// yog_api::ui_include!("mod_list");
348/// ```
349///
350/// `<name>` is the `.yui` file's path under `src/` without the extension
351/// (`"screens/options"` for `src/screens/options.yui`). `yog build`,
352/// `yog check` and `yog ui gen` write the code to
353/// `.yog-build/generated/<name>.g.rs` (the build crate's own directory,
354/// `CARGO_MANIFEST_DIR`), so it's never committed and never stale.
355#[macro_export]
356macro_rules! ui_include {
357    ($name:literal) => {
358        include!(concat!(
359            env!("CARGO_MANIFEST_DIR"),
360            "/generated/",
361            $name,
362            ".g.rs"
363        ));
364    };
365}
366
367/// Pulls the mod's `.yres` resources in as module `res` — one type per
368/// resource set, with an accessor per entry:
369///
370/// ```ignore
371/// // src/lib.rs
372/// yog_api::resources!();
373///
374/// use crate::res::{Icons, Strings};
375/// let title = Strings::screen_title();        // the text in the player's language
376/// let count = Strings::mods_count(58);        // "{count} mods" → "58 mods"
377/// let logo = Icons::logo();                   // an image source, "mymod:images/logo"
378/// ```
379///
380/// `yog build`, `yog check` and `yog res check` generate one
381/// `.yog-build/generated/resources/<Set>.g.rs` per `resources/**/<Set>.yres`
382/// plus `.yog-build/generated/resources.g.rs` including them all (see
383/// Yog-IDLE's `docs/yres.md`); never committed, never stale.
384#[macro_export]
385macro_rules! resources {
386    () => {
387        pub mod res {
388            include!(concat!(
389                env!("CARGO_MANIFEST_DIR"),
390                "/generated/resources.g.rs"
391            ));
392        }
393    };
394}
395
396/// Pulls the mod's blocks and items — `content/**/*.yblock` and `*.yitem`,
397/// made in Yog-IDLE's model editor — in as module `content`, and registers
398/// them (every file without `"register": "manual"`) right after
399/// `Mod::register`:
400///
401/// ```ignore
402/// // src/lib.rs — that's all it takes
403/// yog_api::content!();
404///
405/// // Ids and definitions, for code that needs them:
406/// use crate::content::{Blocks, Items};
407/// let id: &str = Blocks::RUBY_ORE;                 // "mymod:ruby_ore"
408/// registry.register_item(Items::ruby().max_stack(16)); // a "manual" one, tweaked
409/// ```
410///
411/// `yog build`, `yog check` and `yog content check` generate
412/// `.yog-build/generated/yog-content.g.rs` — never committed, never stale.
413/// The models, blockstates, loot tables, tags and lang entries go into the
414/// `.yog` as assets. See Yog-IDLE's `docs/blocks-items.md`.
415#[macro_export]
416macro_rules! content {
417    () => {
418        pub mod content {
419            include!(concat!(
420                env!("CARGO_MANIFEST_DIR"),
421                "/generated/yog-content.g.rs"
422            ));
423        }
424    };
425}
426
427/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
428/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
429pub mod inventory {
430    pub use yog_inventory::*;
431}