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