use crate::{
StereoKitError,
maths::Bool32T,
system::{Log, LogLevel},
tex::TexFormat,
tools::os_api::get_assets_dir,
};
#[cfg(target_os = "android")]
#[cfg(feature = "no-event-loop")]
use android_activity::{AndroidApp, MainEvent, PollEvent};
use std::{
cell::RefCell,
ffi::{CStr, CString, c_char, c_void},
fmt::{self, Formatter},
path::Path,
ptr::null_mut,
rc::Rc,
};
#[cfg(target_os = "android")]
#[cfg(feature = "event-loop")]
use winit::platform::android::{
EventLoopBuilderExtAndroid,
activity::{AndroidApp, MainEvent, PollEvent},
};
#[cfg(feature = "event-loop")]
use crate::framework::{StepperAction, Steppers};
#[cfg(feature = "event-loop")]
use std::collections::VecDeque;
#[cfg(feature = "event-loop")]
use winit::{
event::Event,
event_loop::{ControlFlow, EventLoop, EventLoopProxy},
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum DisplayMode {
MixedReality = 0,
Flatscreen = 1,
None = 2,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum AppMode {
None = 0,
XR = 1,
Simulator = 2,
Window = 3,
Offscreen = 4,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum DepthMode {
Default = 0,
D16 = 1,
D32 = 2,
Stencil = 3,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum DisplayBlend {
None = 0,
Opaque = 1,
Additive = 2,
Blend = 4,
AnyTransparent = 6,
}
#[derive(Default, Debug, Clone, PartialEq)]
#[repr(C)]
pub struct SystemInfo {
display_width: i32,
display_height: i32,
spatial_bridge_present: Bool32T,
perception_bridge_present: Bool32T,
eye_tracking_present: Bool32T,
overlay_app: Bool32T,
world_occlusion_present: Bool32T,
world_raycast_present: Bool32T,
}
impl SystemInfo {
pub fn get_display_width(&self) -> i32 {
self.display_width
}
pub fn get_display_height(&self) -> i32 {
self.display_height
}
pub fn get_spatial_bridge_present(&self) -> bool {
self.spatial_bridge_present != 0
}
pub fn get_perception_bridge_present(&self) -> bool {
self.perception_bridge_present != 0
}
pub fn get_eye_tracking_present(&self) -> bool {
self.eye_tracking_present != 0
}
pub fn get_overlay_app(&self) -> bool {
self.overlay_app != 0
}
pub fn get_world_occlusion_present(&self) -> bool {
self.world_occlusion_present != 0
}
pub fn get_world_raycast_present(&self) -> bool {
self.world_raycast_present != 0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum OriginMode {
Local = 0,
Floor = 1,
Stage = 2,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub enum AppFocus {
Active = 0,
Background = 1,
Hidden = 2,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub enum StandbyMode {
Default = 0,
Pause = 1,
Slow = 2,
None = 3,
}
unsafe extern "C" {
pub fn sk_init(settings: SkSettings) -> Bool32T;
pub fn sk_set_window(window: *mut c_void);
pub fn sk_set_window_xam(window: *mut c_void);
pub fn sk_shutdown();
pub fn sk_shutdown_unsafe();
pub fn sk_quit(quit_reason: QuitReason);
pub fn sk_step(app_step: Option<unsafe extern "C" fn()>) -> Bool32T;
pub fn sk_run(app_step: Option<unsafe extern "C" fn()>, app_shutdown: Option<unsafe extern "C" fn()>);
pub fn sk_run_data(
app_step: Option<unsafe extern "C" fn(step_data: *mut c_void)>,
step_data: *mut c_void,
app_shutdown: Option<unsafe extern "C" fn(shutdown_data: *mut c_void)>,
shutdown_data: *mut c_void,
);
pub fn sk_is_stepping() -> Bool32T;
pub fn sk_active_display_mode() -> DisplayMode;
pub fn sk_get_settings() -> SkSettings;
pub fn sk_system_info() -> SystemInfo;
pub fn sk_version_name() -> *const c_char;
pub fn sk_version_id() -> u64;
pub fn sk_app_focus() -> AppFocus;
pub fn sk_get_quit_reason() -> QuitReason;
}
pub const DEFAULT_NAME: *const c_char = {
const BYTES: &[u8] = b"StereoKitApp\0";
BYTES.as_ptr().cast()
};
#[derive(Debug, Clone)]
#[repr(C)]
pub struct SkSettings {
pub app_name: *const c_char,
pub assets_folder: *const c_char,
pub mode: AppMode,
pub blend_preference: DisplayBlend,
pub no_flatscreen_fallback: Bool32T,
pub depth_mode: DepthMode,
pub log_filter: LogLevel,
pub color_format: TexFormat,
pub overlay_app: Bool32T,
pub overlay_priority: u32,
pub flatscreen_pos_x: i32,
pub flatscreen_pos_y: i32,
pub flatscreen_width: i32,
pub flatscreen_height: i32,
pub disable_desktop_input_window: Bool32T,
pub disable_unfocused_sleep: Bool32T,
pub render_scaling: f32,
pub render_multisample: i32,
pub origin: OriginMode,
pub omit_empty_frames: Bool32T,
pub standby_mode: StandbyMode,
pub android_java_vm: *mut c_void,
pub android_activity: *mut c_void,
}
impl Default for SkSettings {
fn default() -> Self {
Self {
app_name: DEFAULT_NAME,
assets_folder: Self::assets_folder(get_assets_dir()),
mode: AppMode::XR,
blend_preference: DisplayBlend::None,
no_flatscreen_fallback: 0,
depth_mode: DepthMode::Default,
color_format: TexFormat::None,
log_filter: LogLevel::None,
overlay_app: 0,
overlay_priority: 0,
flatscreen_pos_x: 0,
flatscreen_pos_y: 0,
flatscreen_width: 0,
flatscreen_height: 0,
disable_desktop_input_window: 0,
disable_unfocused_sleep: 0,
render_scaling: 1.0,
render_multisample: 1,
origin: OriginMode::Local,
omit_empty_frames: 0,
standby_mode: StandbyMode::Default,
android_java_vm: null_mut(),
android_activity: null_mut(),
}
}
}
impl fmt::Display for SkSettings {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl SkSettings {
pub fn app_name(&mut self, app_name: impl AsRef<str>) -> &mut Self {
let c_str = CString::new(app_name.as_ref()).unwrap();
self.app_name = c_str.into_raw();
self
}
fn assets_folder(assets_folder: impl AsRef<Path>) -> *mut c_char {
let c_str = CString::new(assets_folder.as_ref().to_str().unwrap()).unwrap();
c_str.into_raw()
}
pub fn mode(&mut self, app_mode: AppMode) -> &mut Self {
self.mode = app_mode;
self
}
pub fn no_flatscreen_fallback(&mut self, no_flatscreen_fallback: bool) -> &mut Self {
self.no_flatscreen_fallback = no_flatscreen_fallback as Bool32T;
self
}
pub fn blend_preference(&mut self, blend_preference: DisplayBlend) -> &mut Self {
self.blend_preference = blend_preference;
self
}
pub fn depth_mode(&mut self, depth_mode: DepthMode) -> &mut Self {
self.depth_mode = depth_mode;
self
}
pub fn color_format(&mut self, color_format: TexFormat) -> &mut Self {
self.color_format = color_format;
self
}
pub fn log_filter(&mut self, log_filter: LogLevel) -> &mut Self {
self.log_filter = log_filter;
self
}
pub fn overlay_app(&mut self, overlay_app: bool) -> &mut Self {
self.overlay_app = overlay_app as Bool32T;
self
}
pub fn overlay_priority(&mut self, overlay_priority: u32) -> &mut Self {
self.overlay_priority = overlay_priority;
self
}
pub fn flatscreen_pos_x(&mut self, flatscreen_pos_x: i32) -> &mut Self {
self.flatscreen_pos_x = flatscreen_pos_x;
self
}
pub fn flatscreen_pos_y(&mut self, flatscreen_pos_y: i32) -> &mut Self {
self.flatscreen_pos_y = flatscreen_pos_y;
self
}
pub fn flatscreen_pos(&mut self, flatscreen_pos_x: i32, flatscreen_pos_y: i32) -> &mut Self {
self.flatscreen_pos_x = flatscreen_pos_x;
self.flatscreen_pos_y = flatscreen_pos_y;
self
}
pub fn flatscreen_width(&mut self, flatscreen_width: i32) -> &mut Self {
self.flatscreen_width = flatscreen_width;
self
}
pub fn flatscreen_height(&mut self, flatscreen_height: i32) -> &mut Self {
self.flatscreen_height = flatscreen_height;
self
}
pub fn flatscreen_size(&mut self, flatscreen_width: i32, flatscreen_height: i32) -> &mut Self {
self.flatscreen_width = flatscreen_width;
self.flatscreen_height = flatscreen_height;
self
}
pub fn disable_desktop_input_window(&mut self, disabled_desktop_input_window: bool) -> &mut Self {
self.disable_desktop_input_window = disabled_desktop_input_window as Bool32T;
self
}
#[deprecated(since = "0.40.0", note = "please use `standby_mode = StandbyMode::None` instead")]
pub fn disable_unfocused_sleep(&mut self, disable_unfocused_sleep: bool) -> &mut Self {
self.disable_unfocused_sleep = disable_unfocused_sleep as Bool32T;
self
}
pub fn render_scaling(&mut self, render_scaling: f32) -> &mut Self {
self.render_scaling = render_scaling;
self
}
pub fn render_multisample(&mut self, render_multisample: i32) -> &mut Self {
self.render_multisample = render_multisample;
self
}
pub fn origin(&mut self, origin_mode: OriginMode) -> &mut Self {
self.origin = origin_mode;
self
}
pub fn omit_empty_frames(&mut self, origin_mode: bool) -> &mut Self {
self.omit_empty_frames = origin_mode as Bool32T;
self
}
pub fn standby_mode(&mut self, mode: StandbyMode) -> &mut Self {
self.standby_mode = mode;
self
}
}
#[cfg(feature = "event-loop")]
impl SkSettings {
#[cfg(target_os = "android")]
pub fn init_with_event_loop(&mut self, app: AndroidApp) -> Result<(Sk, EventLoop<StepperAction>), StereoKitError> {
Sk::init_with_event_loop(self, app)
}
#[cfg(not(target_os = "android"))]
pub fn init_with_event_loop(&mut self) -> Result<(Sk, EventLoop<StepperAction>), StereoKitError> {
Sk::init_with_event_loop(self)
}
}
#[cfg(feature = "no-event-loop")]
impl SkSettings {
#[cfg(target_os = "android")]
pub fn init(&mut self, app: AndroidApp) -> Result<Sk, StereoKitError> {
Sk::init(self, app)
}
#[cfg(not(target_os = "android"))]
pub fn init(&mut self) -> Result<Sk, StereoKitError> {
Sk::init(self)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum QuitReason {
None = 0,
User = 1,
Error = 2,
InitializationFailed = 3,
SessionLost = 4,
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct SkInfo {
settings: SkSettings,
#[cfg(feature = "event-loop")]
event_loop_proxy: Option<EventLoopProxy<StepperAction>>,
#[cfg(target_os = "android")]
android_app: AndroidApp,
}
impl SkInfo {
pub fn get_settings(&self) -> SkSettings {
self.settings.clone()
}
pub fn get_system(&self) -> SystemInfo {
unsafe { sk_system_info() }
}
#[cfg(feature = "event-loop")]
pub fn get_event_loop_proxy(&self) -> Option<EventLoopProxy<StepperAction>> {
self.event_loop_proxy.clone()
}
#[cfg(target_os = "android")]
pub fn get_android_app(&self) -> &AndroidApp {
&self.android_app
}
pub fn settings_from(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> SkSettings {
if sk_info.is_none() {
Log::err("The stepper must be initialized. SkInfo::setting_from(??) returns an invalid default value.");
return SkSettings::default();
}
let rc_sk = sk_info.as_ref().unwrap();
let sk = rc_sk.as_ref();
sk.borrow().get_settings()
}
pub fn system_from(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> SystemInfo {
if sk_info.is_none() {
Log::err("The stepper must be initialized. SkInfo::system_from(??) returns an invalid default value.");
return SystemInfo::default();
}
let rc_sk = sk_info.as_ref().unwrap();
let sk = rc_sk.as_ref();
sk.borrow().get_system()
}
#[cfg(feature = "event-loop")]
pub fn event_loop_proxy_from(sk_info: &Option<Rc<RefCell<SkInfo>>>) -> Option<EventLoopProxy<StepperAction>> {
if sk_info.is_none() {
Log::err("The stepper must be initialized. SkInfo::event_loop_proxy_from(??) returns None.");
return None;
}
let rc_sk = sk_info.as_ref().unwrap();
let sk = rc_sk.as_ref();
sk.borrow().get_event_loop_proxy()
}
#[cfg(feature = "event-loop")]
pub fn send_event(sk_info: &Option<Rc<RefCell<SkInfo>>>, message: StepperAction) {
if let Some(proxy) = Self::event_loop_proxy_from(sk_info) {
proxy.send_event(message).unwrap();
} else {
Log::err("The stepper must be initialized. SkInfo::send_event(??) not sent.");
}
}
#[cfg(feature = "event-loop")]
pub fn get_message_closure(
sk_info: Option<Rc<RefCell<SkInfo>>>,
id: impl AsRef<str>,
key: &str,
) -> impl Fn(String) {
let sk_info = sk_info.clone();
let key = key.to_string();
let id = id.as_ref().to_string();
Box::new(move |value: String| {
SkInfo::send_event(&sk_info, StepperAction::event(id.as_str(), &key, &value));
})
}
}
pub struct MainThreadToken {
#[cfg(feature = "event-loop")]
pub(crate) event_report: Vec<StepperAction>,
}
#[cfg(feature = "event-loop")]
impl MainThreadToken {
pub fn get_event_report(&self) -> &Vec<StepperAction> {
&self.event_report
}
}
pub struct Sk {
sk_info: Rc<RefCell<SkInfo>>,
token: MainThreadToken,
#[cfg(feature = "event-loop")]
pub(crate) steppers: Steppers,
#[cfg(feature = "event-loop")]
pub(crate) actions: VecDeque<Box<dyn FnMut()>>,
}
impl Sk {
#[cfg(target_os = "android")]
pub fn poll_first_events(app: &AndroidApp) {
let mut ready_to_go = false;
while !ready_to_go {
app.poll_events(None, |event| match event {
PollEvent::Main(main_event) => {
Log::diag(format!("MainEvent {:?} ", main_event));
match main_event {
MainEvent::GainedFocus { .. } => {
ready_to_go = true;
}
_ => {
ready_to_go = false;
}
}
}
otherwise => Log::diag(format!("PollEvent {:?} ", otherwise)),
})
}
}
#[cfg(feature = "no-event-loop")]
#[cfg(target_os = "android")]
pub fn init(settings: &mut SkSettings, app: AndroidApp) -> Result<Sk, StereoKitError> {
let (vm_pointer, jobject_pointer) = {
{
let context = ndk_context::android_context();
(context.vm(), context.context())
}
};
settings.android_java_vm = vm_pointer;
settings.android_activity = jobject_pointer;
Log::diag(format!("sk_init : context: {:?} / jvm: {:?}", vm_pointer, jobject_pointer));
match unsafe {
Log::info("Before init >>>");
let val = sk_init(settings.clone()) != 0;
Log::info("<<< After init");
val
} {
true => {
let sk_info = Rc::new(RefCell::new(SkInfo {
android_app: app,
settings: unsafe { sk_get_settings() },
#[cfg(feature = "event-loop")]
event_loop_proxy: None,
}));
Ok(Sk {
sk_info: sk_info.clone(),
token: MainThreadToken {
#[cfg(feature = "event-loop")]
event_report: vec![],
},
#[cfg(feature = "event-loop")]
steppers: Steppers::new(sk_info.clone()),
#[cfg(feature = "event-loop")]
actions: VecDeque::new(),
})
}
false => Err(StereoKitError::SkInit(settings.to_string())),
}
}
#[cfg(not(target_os = "android"))]
pub fn init(settings: &SkSettings) -> Result<Sk, StereoKitError> {
match unsafe {
Log::info("Before init >>>");
let val = sk_init(settings.clone()) != 0;
Log::info("<<< After init");
val
} {
true => {
let sk_info = Rc::new(RefCell::new(SkInfo {
settings: unsafe { sk_get_settings() },
#[cfg(feature = "event-loop")]
event_loop_proxy: None,
}));
Ok(Sk {
sk_info: sk_info.clone(),
token: MainThreadToken {
#[cfg(feature = "event-loop")]
event_report: vec![],
},
#[cfg(feature = "event-loop")]
steppers: Steppers::new(sk_info.clone()),
#[cfg(feature = "event-loop")]
actions: VecDeque::new(),
})
}
false => Err(StereoKitError::SkInit(settings.to_string())),
}
}
pub fn step(&self) -> Option<&MainThreadToken> {
if unsafe { sk_step(None) } == 0 {
return None;
}
Some(&self.token)
}
pub fn main_thread_token(&mut self) -> &MainThreadToken {
&self.token
}
pub fn get_active_display_mode(&self) -> DisplayMode {
unsafe { sk_active_display_mode() }
}
pub fn get_app_focus(&self) -> AppFocus {
unsafe { sk_app_focus() }
}
pub fn get_sk_info_clone(&self) -> Rc<RefCell<SkInfo>> {
self.sk_info.clone()
}
pub fn get_settings(&self) -> SkSettings {
unsafe { sk_get_settings() }
}
pub fn get_system(&self) -> SystemInfo {
unsafe { sk_system_info() }
}
pub fn get_version_id(&self) -> u64 {
unsafe { sk_version_id() }
}
pub fn get_version_name(&self) -> &str {
unsafe { CStr::from_ptr(sk_version_name()) }.to_str().unwrap()
}
pub fn quit(&self, quit_reason: Option<QuitReason>) {
let quit_reason = quit_reason.unwrap_or(QuitReason::User);
unsafe { sk_quit(quit_reason) }
}
pub fn get_quit_reason(&self) -> QuitReason {
unsafe { sk_get_quit_reason() }
}
pub fn shutdown() {
unsafe { sk_shutdown() }
if cfg!(target_os = "android") {
std::process::exit(0);
}
}
}
#[cfg(feature = "event-loop")]
impl Sk {
#[cfg(target_os = "android")]
pub fn init_with_event_loop(
settings: &mut SkSettings,
app: AndroidApp,
) -> Result<(Sk, EventLoop<StepperAction>), StereoKitError> {
Sk::poll_first_events(&app);
let event_loop = EventLoop::<StepperAction>::with_user_event().with_android_app(app.clone()).build()?;
let event_loop_proxy = event_loop.create_proxy();
let (vm_pointer, jobject_pointer) = {
{
let context = ndk_context::android_context();
(context.vm(), context.context())
}
};
settings.android_java_vm = vm_pointer;
settings.android_activity = jobject_pointer;
Log::diag(format!("sk_init : context: {:?} / jvm: {:?}", vm_pointer, jobject_pointer));
match unsafe {
Log::info("Before init >>>");
let val = sk_init(settings.clone()) != 0;
Log::info("<<< After init");
val
} {
true => {
let sk_info = Rc::new(RefCell::new(SkInfo {
settings: settings.clone(),
event_loop_proxy: Some(event_loop_proxy),
android_app: app,
}));
Ok((
Sk {
sk_info: sk_info.clone(),
token: MainThreadToken { event_report: vec![] },
steppers: Steppers::new(sk_info.clone()),
actions: VecDeque::new(),
},
event_loop,
))
}
false => Err(StereoKitError::SkInit(settings.to_string())),
}
}
#[cfg(not(target_os = "android"))]
pub fn init_with_event_loop(settings: &mut SkSettings) -> Result<(Sk, EventLoop<StepperAction>), StereoKitError> {
let event_loop = EventLoop::<StepperAction>::with_user_event().build()?;
let event_loop_proxy = event_loop.create_proxy();
let (vm_pointer, jobject_pointer) = (null_mut::<c_void>(), null_mut::<c_void>());
settings.android_java_vm = vm_pointer;
settings.android_activity = jobject_pointer;
Log::info(format!("SK_INIT ::: context {vm_pointer:?}/jvm : {jobject_pointer:?}"));
match unsafe {
Log::info("Before init >>>");
let val = sk_init(settings.clone()) != 0;
Log::info("<<< After init");
val
} {
true => {
let sk_info = Rc::new(RefCell::new(SkInfo {
settings: settings.clone(),
event_loop_proxy: Some(event_loop_proxy),
}));
Ok((
Sk {
sk_info: sk_info.clone(),
token: MainThreadToken { event_report: vec![] },
steppers: Steppers::new(sk_info.clone()),
actions: VecDeque::new(),
},
event_loop,
))
}
false => Err(StereoKitError::SkInit(settings.to_string())),
}
}
pub fn swap_steppers(&mut self, steppers: &mut Steppers) {
std::mem::swap(&mut self.steppers, steppers);
}
pub fn execute_on_main<F: FnMut() + 'static>(&mut self, action: F) {
self.actions.push_back(Box::new(action))
}
pub fn send_event(&mut self, action: StepperAction) {
self.steppers.send_event(action);
}
pub fn get_steppers_count(&self) -> usize {
self.steppers.get_count()
}
pub fn get_steppers(&self) -> &Steppers {
&self.steppers
}
pub fn get_event_loop_proxy(&self) -> Option<EventLoopProxy<StepperAction>> {
let sk = self.sk_info.as_ref();
sk.borrow().get_event_loop_proxy()
}
#[deprecated(since = "0.40.0", note = "see [crate::framework::SkClosures] instead")]
pub fn step_looped<F: FnMut(&mut Sk)>(&mut self, on_step: &mut F) -> bool {
if unsafe { sk_step(None) } == 0 {
return false;
}
if !self.steppers.step(&mut self.token) {
self.quit(None)
};
while let Some(mut action) = self.actions.pop_front() {
action();
}
on_step(self);
true
}
#[deprecated(since = "0.40.0", note = "see [crate::framework::SkClosures] instead")]
pub fn run<U: FnMut(&mut Sk), S: FnMut(&mut Sk)>(
mut self,
event_loop: EventLoop<StepperAction>,
mut on_step: U,
mut on_shutdown: S,
) {
event_loop.set_control_flow(ControlFlow::Poll);
#[allow(deprecated)]
event_loop
.run(move |event, elwt| match event {
Event::NewEvents(_start_cause) => {} Event::WindowEvent { window_id, event } => Log::diag(format!("WindowEvent {window_id:?} -> {event:?}")),
Event::DeviceEvent { device_id, event } => Log::diag(format!("DeviceEvent {device_id:?} -> {event:?}")),
Event::UserEvent(action) => {
Log::diag(format!("UserEvent {action:?}"));
self.send_event(action);
}
Event::Suspended => Log::info("Suspended !!"),
Event::Resumed => Log::info("Resumed !!"),
Event::AboutToWait => {
if !&self.step_looped(&mut on_step) {
elwt.exit()
}
}
Event::LoopExiting => {
Log::info("LoopExiting !!");
on_shutdown(&mut self);
}
Event::MemoryWarning => Log::warn("MemoryWarning !!"),
})
.unwrap_or_else(|e| {
Log::err(format!("!!!event_loop error closing!! : {e}"));
});
}
}