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 `.yui` components (types implementing
153/// [`ui::component::Component`]) when the mod's library loads, so screens
154/// can use `<Name …/>` and Yog-IDLE's designer host can draw them. The code
155/// `yog build` generates from a `<Component>` `.yui` calls this — mods
156/// don't write it themselves.
157#[macro_export]
158macro_rules! ui_component {
159 ($($ty:ty),+ $(,)?) => {
160 const _: () = {
161 #[$crate::ctor::ctor(unsafe, crate_path = $crate::ctor)]
162 fn __yog_register_ui_components() {
163 $( $crate::ui::component::register::<$ty>(); )+
164 }
165 };
166 };
167}
168
169/// Compiles in the mod's component `.yui` files that have no code-behind
170/// `.rs` (those with one are pulled in by its `ui_include!`) — call it once,
171/// in `src/lib.rs`:
172///
173/// ```ignore
174/// yog_api::ui_components!();
175/// ```
176///
177/// `yog build`, `yog check` and `yog ui gen` write the list to
178/// `.yog-build/generated/components.g.rs` (a module per component).
179#[macro_export]
180macro_rules! ui_components {
181 () => {
182 include!(concat!(env!("CARGO_MANIFEST_DIR"), "/generated/components.g.rs"));
183 };
184}
185
186/// Internal: set by `export_mod!` before calling `Mod::register`.
187/// Used by `yog_api::interop::current_mod_id()` so `Interop::export` knows
188/// which mod is calling.
189#[doc(hidden)]
190pub fn __set_current_mod_id(id: &str) {
191 CURRENT_MOD_ID.with(|cell| cell.replace(Some(id.to_string())));
192}
193
194/// Internal: the current mod's id, set during `yog_mod_register`.
195#[doc(hidden)]
196pub fn __current_mod_id() -> Option<String> {
197 CURRENT_MOD_ID.with(|cell| cell.borrow().clone())
198}
199
200/// Internal: the global `YogApi` pointer, set by every mod's `yog_mod_register`
201/// (it's the same singleton table for the whole process). Process-wide (not
202/// thread-local) because `#[yog_export]`-generated wrappers read it whenever
203/// an exported function is *called*, which may happen on any thread, long
204/// after the loading thread's `yog_mod_register` returned.
205#[doc(hidden)]
206pub fn __set_current_api_ptr(ptr: usize) {
207 API_PTR.store(ptr, std::sync::atomic::Ordering::Release);
208}
209
210#[doc(hidden)]
211pub fn __current_api_ptr() -> usize {
212 API_PTR.load(std::sync::atomic::Ordering::Acquire)
213}
214
215static API_PTR: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
216
217std::thread_local! {
218 static CURRENT_MOD_ID: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
219}
220
221// ── Auto-export registry ─────────────────────────────────────────────────
222
223#[doc(hidden)]
224pub struct YogExportEntry {
225 pub name: &'static str,
226 pub ptr: usize,
227}
228
229#[doc(hidden)]
230pub struct YogImportEntry {
231 pub mod_id: &'static str,
232 pub symbol: &'static str,
233 pub bind_fn: usize,
234}
235
236#[doc(hidden)]
237pub fn __yog_export_registry() -> &'static std::sync::Mutex<Vec<YogExportEntry>> {
238 static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogExportEntry>>> =
239 std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
240 ®
241}
242
243/// The `register` functions of `yog_api::content!()` — run by
244/// `export_mod!` after `Mod::register`.
245#[doc(hidden)]
246pub fn __yog_content_registrars() -> &'static std::sync::Mutex<Vec<fn(&mut Registry)>> {
247 static REG: std::sync::Mutex<Vec<fn(&mut Registry)>> = std::sync::Mutex::new(Vec::new());
248 ®
249}
250
251#[doc(hidden)]
252pub fn __yog_import_registry() -> &'static std::sync::Mutex<Vec<YogImportEntry>> {
253 static REG: std::sync::LazyLock<std::sync::Mutex<Vec<YogImportEntry>>> =
254 std::sync::LazyLock::new(|| std::sync::Mutex::new(Vec::new()));
255 ®
256}
257
258/// Called by `export_mod!` after registration. Iterates pending imports and
259/// resolves them via the interop table. Also exported as `#[no_mangle]` so the
260/// runtime can call it after all mods are loaded (final resolution pass).
261#[doc(hidden)]
262pub unsafe fn __yog_resolve_pending_imports(registry: &crate::Registry) {
263 let interop = registry.interop();
264 for entry in __yog_import_registry().lock().unwrap().iter() {
265 if let Some(ptr) = interop.import_raw(entry.mod_id, entry.symbol) {
266 let bind: unsafe extern "C" fn(*const std::ffi::c_void) =
267 std::mem::transmute(entry.bind_fn);
268 bind(ptr);
269 }
270 }
271}
272
273pub use yog_book::{
274 crafting_page, entity_page, image_page, pattern_page, relations_page, smelting_page,
275 spotlight_page, text_page, text_page_titled,
276};
277pub use yog_book::{Book, BookCategory, BookEntry, BookMacro, BookPage, BookRegistry};
278pub use yog_book::{BookFontRegistry, BookRenderer};
279pub use yog_command::CommandContext;
280pub use yog_config::Config;
281pub use yog_core::{BlockPos, Server};
282pub use yog_dimensions::{ChunkWriter, YogDimensionDef, YogDimensionTypeDef};
283pub use yog_entity::Entity;
284pub use yog_event::{
285 AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
286 ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
287 EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent, ItemPickupEvent,
288 KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent, PlayerLeaveEvent,
289 PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent, UseBlockEvent,
290 UseItemEvent,
291};
292pub use yog_inventory::{InventoryDef, SlotLayout};
293#[doc(inline)]
294pub use yog_network::packet;
295pub use yog_network::{Packet, PacketEvent, PacketField};
296pub use yog_player::Player;
297pub use yog_registry::{
298 AdvancementReward, BlockDef, BookRecipe, FoodDef, FurnaceRecipe, ItemDef, ItemModifier,
299 ShapedRecipe, ShapelessRecipe, StartupGrant,
300};
301pub use yog_storage::{Storage, StorageScope, Value};
302pub use yog_ui::{widget, Align, Dock, FlexDir, FocusStyle, LayoutNode, Rect, Stretch, UiRoot, Units};
303pub use yog_world::World;
304
305/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
306pub use yog_logging::{error, info, warn};
307
308/// Core types and handles.
309pub mod core {
310 pub use yog_core::*;
311}
312
313/// Events and the subscription registry.
314pub mod event {
315 pub use yog_event::*;
316}
317
318/// World access (block get/set, dimensions).
319pub mod world {
320 pub use yog_world::*;
321}
322
323/// Entity access (teleport, position, health, ... by UUID).
324pub mod entity {
325 pub use yog_entity::*;
326}
327
328/// Player access (give item, teleport).
329pub mod player {
330 pub use yog_player::*;
331}
332
333/// Content registration (custom items / blocks / food).
334pub mod content {
335 pub use yog_registry::*;
336}
337
338/// Networking (raw-byte packets over channels).
339pub mod network {
340 pub use yog_network::*;
341}
342
343/// Commands.
344pub mod command {
345 pub use yog_command::*;
346}
347
348/// Custom dimensions — dimension-type definitions and runtime chunk generation.
349pub mod dimension {
350 pub use yog_dimensions::*;
351}
352
353/// Persistent key-value storage for mod data.
354pub mod storage {
355 pub use yog_storage::*;
356}
357
358/// Mod configuration (typed key/value files).
359pub mod config {
360 pub use yog_config::*;
361}
362
363/// In-game book/documentation system (Patchouli-like).
364pub mod book {
365 pub use yog_book::*;
366}
367
368/// UI framework — flexbox layout + widgets on top of yog-gfx.
369pub mod ui {
370 pub use yog_ui::*;
371}
372
373/// Pulls a `.yui` screen's generated code into the current module — the
374/// code-behind's half of `src/<name>.yui`:
375///
376/// ```ignore
377/// // src/mod_list.rs, next to src/mod_list.yui
378/// yog_api::ui_include!("mod_list");
379/// ```
380///
381/// `<name>` is the `.yui` file's path under `src/` without the extension
382/// (`"screens/options"` for `src/screens/options.yui`). `yog build`,
383/// `yog check` and `yog ui gen` write the code to
384/// `.yog-build/generated/<name>.g.rs` (the build crate's own directory,
385/// `CARGO_MANIFEST_DIR`), so it's never committed and never stale.
386#[macro_export]
387macro_rules! ui_include {
388 ($name:literal) => {
389 include!(concat!(env!("CARGO_MANIFEST_DIR"), "/generated/", $name, ".g.rs"));
390 };
391}
392
393/// Pulls the mod's `.yres` resources in as module `res` — one type per
394/// resource set, with an accessor per entry:
395///
396/// ```ignore
397/// // src/lib.rs
398/// yog_api::resources!();
399///
400/// use crate::res::{Icons, Strings};
401/// let title = Strings::screen_title(); // the text in the player's language
402/// let count = Strings::mods_count(58); // "{count} mods" → "58 mods"
403/// let logo = Icons::logo(); // an image source, "mymod:images/logo"
404/// ```
405///
406/// `yog build`, `yog check` and `yog res check` generate one
407/// `.yog-build/generated/resources/<Set>.g.rs` per `resources/**/<Set>.yres`
408/// plus `.yog-build/generated/resources.g.rs` including them all (see
409/// Yog-IDLE's `docs/yres.md`); never committed, never stale.
410#[macro_export]
411macro_rules! resources {
412 () => {
413 pub mod res {
414 include!(concat!(env!("CARGO_MANIFEST_DIR"), "/generated/resources.g.rs"));
415 }
416 };
417}
418
419/// Pulls the mod's blocks and items — `content/**/*.yblock` and `*.yitem`,
420/// made in Yog-IDLE's model editor — in as module `content`, and registers
421/// them (every file without `"register": "manual"`) right after
422/// `Mod::register`:
423///
424/// ```ignore
425/// // src/lib.rs — that's all it takes
426/// yog_api::content!();
427///
428/// // Ids and definitions, for code that needs them:
429/// use crate::content::{Blocks, Items};
430/// let id: &str = Blocks::RUBY_ORE; // "mymod:ruby_ore"
431/// registry.register_item(Items::ruby().max_stack(16)); // a "manual" one, tweaked
432/// ```
433///
434/// `yog build`, `yog check` and `yog content check` generate
435/// `.yog-build/generated/yog-content.g.rs` — never committed, never stale.
436/// The models, blockstates, loot tables, tags and lang entries go into the
437/// `.yog` as assets. See Yog-IDLE's `docs/blocks-items.md`.
438#[macro_export]
439macro_rules! content {
440 () => {
441 pub mod content {
442 include!(concat!(env!("CARGO_MANIFEST_DIR"), "/generated/yog-content.g.rs"));
443 }
444 };
445}
446
447/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
448/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
449pub mod inventory {
450 pub use yog_inventory::*;
451}