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