unrust/
unrust.rs

1use std::ffi::{c_char, CStr, CString};
2
3mod loader;
4mod logger;
5mod unity;
6
7pub use bevy;
8
9use bevy::prelude::*;
10pub use codegen::generate_csharp;
11pub use inbuilt::*;
12pub use loader::GamePlugin;
13pub use tracing;
14
15pub use unity::{CreateFn, DestroyEntity, DestroyFn, InstantiateEntity, PrefabData, UpdateFn};
16pub use unrust_proc_macro::*;
17
18use crate::{
19    logger::{setup_logging, teardown_logging, LoggerFunc},
20    unity::{start_app, UnityPlugins},
21};
22
23#[repr(C)]
24pub struct UnrustContextWrapper;
25
26pub struct UnrustContext {
27    pub app: App,
28}
29
30static mut GAMEPLUGIN: Option<Box<dyn GamePlugin>> = None;
31
32#[allow(clippy::missing_safety_doc)]
33pub unsafe fn setup_game(game: Box<dyn GamePlugin>) {
34    GAMEPLUGIN = Some(game);
35}
36
37#[no_mangle]
38#[allow(clippy::missing_safety_doc)]
39pub unsafe extern "C" fn load(logger: LoggerFunc) -> *mut UnrustContextWrapper {
40    setup_logging(Box::new(move |level, str| {
41        let len = str.len();
42        let out_str = CString::new(str).unwrap();
43        let out_str = out_str.into_raw();
44        (logger)(level, out_str, len);
45        unsafe {
46            let _ = CString::from_raw(out_str); // drop the string
47        }
48    }));
49
50    let app = App::new();
51
52    tracing::info!("setting up!");
53
54    let ctx = Box::new(UnrustContext { app });
55
56    Box::into_raw(ctx) as *mut UnrustContextWrapper
57}
58
59#[no_mangle]
60#[allow(clippy::missing_safety_doc)]
61pub unsafe extern "C" fn init(
62    ctx: *mut UnrustContextWrapper,
63    base_path: *const c_char,
64    create: CreateFn,
65    update: UpdateFn,
66    destroy: DestroyFn,
67) {
68    let ctx = unsafe { Box::leak(Box::from_raw(ctx as *mut UnrustContext)) };
69    let base_path = unsafe { get_string(base_path) };
70    ctx.app
71        .add_plugins(UnityPlugins::new(base_path, create, update, destroy));
72
73    unsafe {
74        if let Some(game) = &GAMEPLUGIN {
75            game.initialize(&mut ctx.app);
76        } else {
77            tracing::info!("game  not setup!");
78        }
79    }
80
81    start_app(&mut ctx.app);
82}
83
84#[no_mangle]
85pub extern "C" fn register_prefabs(ctx: *mut UnrustContextWrapper, prefabs: PrefabData) {
86    let ctx = unsafe { Box::leak(Box::from_raw(ctx as *mut UnrustContext)) };
87    unsafe {
88        if let Some(game) = &GAMEPLUGIN {
89            game.register(&mut ctx.app.world, prefabs);
90        }
91    }
92}
93
94#[allow(clippy::missing_safety_doc)]
95#[no_mangle]
96pub unsafe extern "C" fn spawn(
97    ctx: *mut UnrustContextWrapper,
98    unity_entity: UnityEntity,
99    inbuilt: *const InbuiltData,
100    len: usize,
101    custom: *const u8,
102    custom_len: usize,
103    custom_state: *const u8,
104    custom_state_len: usize,
105) -> u64 {
106    let components = unsafe { std::slice::from_raw_parts(inbuilt, len) };
107    let ctx = unsafe { Box::leak(Box::from_raw(ctx as *mut UnrustContext)) };
108    let mut entity = ctx.app.world.spawn_empty();
109    entity.insert(unity_entity);
110    ingest_component(&mut entity, components);
111
112    unsafe {
113        if let Some(game) = &GAMEPLUGIN {
114            game.spawn_custom(
115                &mut entity,
116                custom,
117                custom_len,
118                custom_state,
119                custom_state_len,
120            );
121        }
122    }
123
124    entity.id().to_bits()
125}
126
127#[no_mangle]
128pub extern "C" fn tick(ctx: *mut UnrustContextWrapper) {
129    let ctx = unsafe { Box::leak(Box::from_raw(ctx as *mut UnrustContext)) };
130    ctx.app.update();
131}
132
133#[no_mangle]
134pub extern "C" fn unload(ctx: *mut UnrustContextWrapper) {
135    teardown_logging();
136    let _ = unsafe { Box::from_raw(ctx as *mut UnrustContext) };
137    unsafe { GAMEPLUGIN = None };
138}
139
140unsafe fn get_string(base_path: *const i8) -> String {
141    CStr::from_ptr(base_path).to_string_lossy().to_string()
142}