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 domains;
9mod messages;
10mod mods;
11mod reach;
12
13pub use domains::{advancements, body, client, entities, loot, vanilla, video};
14pub use mods::storage;
15pub use reach::{java, mirror, nanite};
16
17// The generated code names `::yog_api`, as in a mod (the host's mirrors are made here).
18extern crate self as yog_api;
19pub use messages::bus::{EventCx, Flow, IntoFlow, emit};
20pub use messages::service::call;
21
22pub use domains::lang::{format_translation, tag_name, tr, tr_args, translation};
23#[doc(hidden)]
24pub use messages::interop::{__yog_export_registry, __yog_serve, YogExportEntry};
25pub use messages::interop::{call_export, export_address};
26pub use mods::registry::{Mod, ModInfo, Registry, UiHandled, close_ui, installed_mods, open_ui};
27pub use yog_gfx::{
28 GfxContext, core as gfx_core, draw2d as gfx_draw2d, gl as gfx_gl, image as gfx_image,
29};
30
31/// Stable C ABI — re-exported so mods don't need a direct `yog-abi` dependency.
32pub use yog_abi::{ABI_VERSION, YogApi};
33#[doc(hidden)]
34pub use yog_abi::{YogBytes, YogReply, YogStatus};
35
36/// Yog Data — what content definitions and book pages' `data`
37/// (`Registry::on_book_page`) are; re-exported for mods.
38pub use yog_data;
39/// `#[derive(Schema)]`: a type read and written as Yog Data (stores, packets, frames).
40pub use yog_data::Schema;
41/// The names Yog shares — kinds, namespaces, lang keys — for a mod to use as the loader does.
42pub use yog_ids;
43/// The sync primitives Yog code uses (locks that outlive a panic) — a mod's too.
44pub use yog_sync as sync;
45
46pub use java::mod_loaded;
47
48/// Inter-mod communication proc-macros.
49pub use yog_interop::{
50 add_method, bench, import, inject, modify_arg, modify_constant, modify_return, modify_variable,
51 nanite, overwrite, redirect, wrap, yog_export, yog_mirror,
52};
53
54/// Re-exported so `#[yog_export]`-generated code can emit `#[::yog_api::ctor::ctor(unsafe)]`
55/// without mods needing a direct `ctor` dependency. Constructors run when the
56/// mod's `cdylib` is loaded — guaranteed to complete before the runtime calls
57/// `yog_mod_register`, so the export registry is fully populated by then.
58pub use ctor;
59
60#[doc(hidden)]
61pub use std::os::raw::c_void as __c_void;
62#[doc(hidden)]
63pub use yog_abi::{YogGfxApi as __YogGfxApi, YogStr as __YogStr};
64
65/// Export a [`Mod`] as a dynamically loadable Yog mod.
66///
67/// Generates the two C-ABI entry points the runtime looks up:
68/// - `yog_abi_version() -> u32` — version check before loading
69/// - `yog_mod_register(*const YogApi, *const c_char)` — registration entry point,
70/// receives the mod's `id` from its manifest
71///
72/// Put this once at the crate root of a `cdylib` mod:
73///
74/// ```ignore
75/// yog_api::export_mod!(MyMod);
76/// ```
77#[macro_export]
78macro_rules! export_mod {
79 ($mod_ty:ty) => {
80 #[unsafe(no_mangle)]
81 pub extern "C" fn yog_abi_version() -> u32 {
82 $crate::ABI_VERSION
83 }
84
85 /// This mod's injections (`#[inject]`), asked for before the game's
86 /// classes load: Yog Data (`yog_event::nanite::Nanites`) to `reply`.
87 #[unsafe(no_mangle)]
88 pub unsafe extern "C" fn yog_mod_nanites(reply: *const $crate::YogReply) {
89 let bytes = $crate::nanite::__yog_nanites();
90 // SAFETY: the runtime passes a live reply sink.
91 unsafe { $crate::YogReply::send(reply, &bytes) };
92 }
93
94 #[unsafe(no_mangle)]
95 pub unsafe extern "C" fn yog_mod_register(
96 api: *const $crate::YogApi,
97 mod_id_ptr: *const ::std::os::raw::c_char,
98 ) {
99 // Parse mod_id from the C string passed by the runtime.
100 let mod_id: &str = if mod_id_ptr.is_null() {
101 "unknown"
102 } else {
103 // SAFETY: a non-null id is a NUL-terminated string from the runtime.
104 match unsafe { ::std::ffi::CStr::from_ptr(mod_id_ptr) }.to_str() {
105 Ok(s) => s,
106 Err(_) => "unknown",
107 }
108 };
109 // Store for interop use (yog_api::interop::current_mod_id()).
110 $crate::__set_current_mod_id(mod_id);
111 // Every mod is handed the same global `YogApi` table, so any
112 // mod's own pointer works for reconstructing a `Registry` inside
113 // an exported function later — even when called from a thread
114 // that never ran `yog_mod_register` itself.
115 $crate::__set_current_api_ptr(api as usize);
116
117 // Catch panics so they never unwind across this `extern "C"` boundary
118 // back into the runtime (which would be undefined behaviour).
119 let outcome = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
120 // SAFETY: the runtime passes a valid YogApi pointer, verified via
121 // yog_abi_version() and abi_version/size checks before this call.
122 let mut registry = unsafe { $crate::Registry::from_raw(api) };
123 // The world's folder known before the mod's own handlers run.
124 $crate::storage::__know_world(&mut registry);
125 <$mod_ty as $crate::Mod>::register(&mut registry);
126
127 // Blocks and items of `content/**/*.yblock|*.yitem`
128 // (`yog_api::content!()`, self-registered by `ctor`).
129 let content: ::std::vec::Vec<fn(&mut $crate::Registry)> =
130 $crate::__yog_content_registrars().lock().clone();
131 for register in content {
132 register(&mut registry);
133 }
134
135 // Every #[yog_export] function (registered by its `ctor`
136 // when the library loaded) provides `mod_id:name`.
137 for entry in $crate::__yog_export_registry().lock().iter() {
138 registry.provide_export(mod_id, entry);
139 }
140
141 // Its injections' handlers, on their points.
142 $crate::nanite::__subscribe(&mut registry);
143
144 // What it keeps, saved with the world (after the mod's own handlers).
145 $crate::storage::__save_with_world(&mut registry);
146 // Its Yog Script objects' records, kept in its stores.
147 $crate::storage::__provide_records(&mut registry);
148 }));
149 if outcome.is_err() {
150 $crate::error!(
151 "mod {} panicked during register",
152 ::core::stringify!($mod_ty)
153 );
154 }
155 }
156
157 /// Yog-IDLE's screen designer: the version of the protocol below.
158 #[unsafe(no_mangle)]
159 pub extern "C" fn yog_ui_designer_abi() -> u32 {
160 $crate::ui::component::designer::ABI
161 }
162
163 /// Yog-IDLE's screen designer: lists this mod's custom components
164 /// and draws them (see `yog_ui::component::designer`). Never called
165 /// in game — only by the designer host, which loads the mod's
166 /// library built for the developer's machine.
167 #[unsafe(no_mangle)]
168 pub unsafe extern "C" fn yog_ui_designer(
169 request: $crate::YogBytes,
170 gfx: *const $crate::__YogGfxApi,
171 user_data: *mut $crate::__c_void,
172 write: $crate::ui::component::designer::WriteFn,
173 ) -> bool {
174 // SAFETY: forwarded under the same contract.
175 unsafe { $crate::ui::component::designer::call(request, gfx, user_data, write) }
176 }
177 };
178}
179
180/// Registers `.yui` components (types implementing
181/// [`ui::component::Component`]) when the mod's library loads, so screens
182/// can use `<Name …/>` and Yog-IDLE's designer host can draw them. The code
183/// `yog build` generates from a `<Component>` `.yui` calls this — mods
184/// don't write it themselves.
185#[macro_export]
186macro_rules! ui_component {
187 ($($ty:ty),+ $(,)?) => {
188 const _: () = {
189 #[$crate::ctor::ctor(unsafe, crate_path = $crate::ctor)]
190 fn __yog_register_ui_components() {
191 $( $crate::ui::component::register::<$ty>(); )+
192 }
193 };
194 };
195}
196
197/// Compiles in the mod's component `.yui` files that have no code-behind
198/// `.rs` (those with one are pulled in by its `ui_include!`) — call it once,
199/// in `src/lib.rs`:
200///
201/// ```ignore
202/// yog_api::ui_components!();
203/// ```
204///
205/// `yog build`, `yog check` and `yog ui gen` write the list to
206/// `obj/generated/components.g.rs` (a module per component).
207#[macro_export]
208macro_rules! ui_components {
209 () => {
210 include!(concat!(
211 env!("CARGO_MANIFEST_DIR"),
212 "/generated/components.g.rs"
213 ));
214 };
215}
216
217/// Internal: set by `export_mod!` before calling `Mod::register`.
218/// Used by `yog_api::interop::current_mod_id()` so `Interop::export` knows
219/// which mod is calling.
220#[doc(hidden)]
221pub fn __set_current_mod_id(id: &str) {
222 CURRENT_MOD_ID.with(|cell| cell.replace(Some(id.to_string())));
223 let _ = MOD_ID.set(id.to_owned());
224}
225
226/// This mod's id (its manifest's), once the runtime has loaded it.
227pub fn mod_id() -> &'static str {
228 MOD_ID.get().map_or("", String::as_str)
229}
230
231/// This mod's id: each mod's library has its own copy of this crate.
232static MOD_ID: yog_sync::OnceLock<String> = yog_sync::OnceLock::new();
233
234/// Internal: the current mod's id, set during `yog_mod_register`.
235#[doc(hidden)]
236pub fn __current_mod_id() -> Option<String> {
237 CURRENT_MOD_ID.with(|cell| cell.borrow().clone())
238}
239
240/// Internal: the global `YogApi` pointer, set by every mod's `yog_mod_register`
241/// (it's the same singleton table for the whole process). Process-wide (not
242/// thread-local) because `#[yog_export]`-generated wrappers read it whenever
243/// an exported function is *called*, which may happen on any thread, long
244/// after the loading thread's `yog_mod_register` returned.
245#[doc(hidden)]
246pub fn __set_current_api_ptr(ptr: usize) {
247 API_PTR.store(ptr, yog_sync::atomic::Ordering::Release);
248}
249
250/// The `register` functions of `yog_api::content!()` — the content of
251/// `content/**` the build generates — run by `export_mod!` after
252/// `Mod::register`.
253#[doc(hidden)]
254pub fn __yog_content_registrars() -> &'static yog_sync::Mutex<Vec<fn(&mut Registry)>> {
255 static REG: yog_sync::Mutex<Vec<fn(&mut Registry)>> = yog_sync::Mutex::new(Vec::new());
256 ®
257}
258
259#[doc(hidden)]
260pub fn __current_api_ptr() -> usize {
261 API_PTR.load(yog_sync::atomic::Ordering::Acquire)
262}
263
264static API_PTR: yog_sync::atomic::AtomicUsize = yog_sync::atomic::AtomicUsize::new(0);
265
266std::thread_local! {
267 static CURRENT_MOD_ID: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
268}
269
270pub use yog_book::BookRenderer;
271pub use yog_command::CommandContext;
272pub use yog_config::Config;
273pub use yog_core::{
274 BlockHit, BlockInfo, BlockPos, EntityInfo, Face, PlacedBlock, PlayerInfo, Point, Rotation,
275 ScreenSlot, SlotStack, Stack, entity_flags, split_state,
276};
277pub use yog_dimensions::{GeneratedChunk, YogDimensionDef, YogDimensionTypeDef};
278pub use yog_event::{Entity, Event, EventPhase, Message, Player, Service, World};
279/// A packet the server or the client received (`on_packet` / `on_client_packet`).
280pub use yog_event::{client::net::Packet as ClientPacketEvent, server::net::Packet as PacketEvent};
281pub use yog_formats::book::{Book, BookCategory, BookEntry, BookPage};
282pub use yog_inventory::{InventoryDef, SlotLayout};
283pub use yog_network::Packet;
284pub use yog_registry::{
285 ArmorDefense, ArmorMaterial, ArmorPiece, ArmorSlot, BlockDef, BlockProperty, Cooking,
286 EntityDef, EntityKind, Facing, FoodDef, ItemDef, Placement, Recipe, RecipeKind, SoundGroup,
287 StartupGrant, ToolItem, ToolMaterial,
288};
289pub use yog_ui::{
290 Align, Dock, FlexDir, FocusStyle, LayoutNode, Rect, Stretch, UiRoot, Units, widget,
291};
292
293/// Logging macros (`yog_api::info!`, `warn!`, `error!`).
294pub use yog_logging::{error, info, warn};
295
296/// Core types and handles.
297pub mod core {
298 pub use yog_core::*;
299}
300
301/// Events by plane — `event::player::session::Join`, `event::block::Break`, … —
302/// subscribed with `Registry::on` / `before` / `after`.
303pub mod event {
304 /// The network's actions as a mod may use them (sending to the server
305 /// declared): this module, not `yog_event`'s.
306 pub use crate::net;
307 pub use yog_event::*;
308}
309
310/// The bus's domains: each one's actions as functions
311/// (`world::get_block(dim, pos)`, `entity::position(uuid)`,
312/// `player::give_item(name, item, count)`) beside its events.
313pub use yog_event::{
314 block, bossbar, chat, entity, game, item, particle, player, scoreboard, sound, tag, world,
315};
316
317pub mod net;
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/// Mod configuration (typed key/value files).
340pub mod config {
341 pub use yog_config::*;
342}
343
344/// Guide books: the `.ybook` format and its renderer.
345pub mod book {
346 pub use yog_book::*;
347 pub use yog_formats::book::*;
348}
349
350/// UI framework — flexbox layout + widgets on top of yog-gfx.
351pub mod ui {
352 pub use yog_ui::*;
353}
354
355/// Pulls a `.yui` screen's generated code into the current module — the
356/// code-behind's half of `src/<name>.yui`:
357///
358/// ```ignore
359/// // src/mod_list.rs, next to src/mod_list.yui
360/// yog_api::ui_include!("mod_list");
361/// ```
362///
363/// `<name>` is the `.yui` file's path under `src/` without the extension
364/// (`"screens/options"` for `src/screens/options.yui`). `yog build`,
365/// `yog check` and `yog ui gen` write the code to
366/// `obj/generated/<name>.g.rs` (the build crate's own directory,
367/// `CARGO_MANIFEST_DIR`), so it's never committed and never stale.
368#[macro_export]
369macro_rules! ui_include {
370 ($name:literal) => {
371 include!(concat!(
372 env!("CARGO_MANIFEST_DIR"),
373 "/generated/",
374 $name,
375 ".g.rs"
376 ));
377 };
378}
379
380/// Pulls the mod's `.yres` resources in as module `res` — one type per
381/// resource set, with an accessor per entry:
382///
383/// ```ignore
384/// // src/lib.rs
385/// yog_api::resources!();
386///
387/// use crate::res::{Icons, Strings};
388/// let title = Strings::screen_title(); // the text in the player's language
389/// let count = Strings::mods_count(58); // "{count} mods" → "58 mods"
390/// let logo = Icons::logo(); // an image source, "mymod:images/logo"
391/// ```
392///
393/// `yog build`, `yog check` and `yog res check` generate one
394/// `obj/generated/resources/<Set>.g.rs` per `resources/**/<Set>.yres`
395/// plus `obj/generated/resources.g.rs` including them all (see
396/// Yog-IDLE's `docs/yres.md`); never committed, never stale.
397#[macro_export]
398macro_rules! resources {
399 () => {
400 pub mod res {
401 include!(concat!(
402 env!("CARGO_MANIFEST_DIR"),
403 "/generated/resources.g.rs"
404 ));
405 }
406 };
407}
408
409/// Pulls the mod's blocks and items — `content/**/*.yblock` and `*.yitem`,
410/// made in Yog-IDLE's model editor — in as module `content`, and registers
411/// them (every file without `"register": "manual"`) right after
412/// `Mod::register`:
413///
414/// ```ignore
415/// // src/lib.rs — that's all it takes
416/// yog_api::content!();
417///
418/// // Ids and definitions, for code that needs them:
419/// use crate::content::{Blocks, Items};
420/// let id: &str = Blocks::RUBY_ORE; // "mymod:ruby_ore"
421/// registry.register_item(Items::ruby().max_stack(16)); // a "manual" one, tweaked
422/// ```
423///
424/// `yog build`, `yog check` and `yog content check` generate
425/// `obj/generated/yog-content.g.rs` — never committed, never stale.
426/// The models, blockstates, loot tables, tags and lang entries go into the
427/// `.yog` as assets. See Yog-IDLE's `docs/blocks-items.md`.
428#[macro_export]
429macro_rules! content {
430 () => {
431 pub mod content {
432 include!(concat!(
433 env!("CARGO_MANIFEST_DIR"),
434 "/generated/yog-content.g.rs"
435 ));
436 }
437 };
438}
439
440/// Inventory framework — real Container/Menu screens (BlockEntity-backed),
441/// as opposed to `ui`'s HUD-drawn overlays. See `yog-inventory`'s DESIGN.md.
442pub mod inventory {
443 pub use yog_inventory::*;
444}