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