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