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