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); }
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}