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