show dialog on panic

This commit is contained in:
Chance 2025-03-30 02:54:07 -04:00 committed by BitSyndicate
parent e09c15a94f
commit 87cef23366
Signed by: bitsyndicate
GPG key ID: 443E4198D6BBA6DE
6 changed files with 319 additions and 6 deletions

View file

@ -42,3 +42,4 @@ typenum = { version = "1.18.0", features = ["const-generics"] }
tobj = { version = "4.0.3", features = ["tokio"] }
ahash = "0.8.11"
wgpu_text = "0.9.2"
native-dialog = "0.7.0"

View file

@ -1,3 +1,7 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Mutex;
pub trait Component: Sized + 'static {
fn update(&mut self, delta_time: f32);
fn serialize(&self) -> Vec<u8>;
@ -11,3 +15,156 @@ pub trait Entity: Sized {
fn serialize(&self) -> Vec<u8>;
fn deserialize(data: &[u8; 6]) -> Self;
}
lazy_static::lazy_static! {
// Global registry mapping component TypeId to a unique bit flag.
static ref COMPONENT_REGISTRY: Mutex<HashMap<TypeId, u64>> = Mutex::new(HashMap::new());
static ref NEXT_COMPONENT_BIT: Mutex<u64> = Mutex::new(1);
}
// To allow dynamic dispatch on components (even though Component itself is not objectsafe)
// we wrap them in an objectsafe trait.
pub trait ComponentObject: Any {
fn update_obj(&mut self, delta_time: f32);
fn serialize_obj(&self) -> Vec<u8>;
fn as_any(&self) -> &dyn Any;
}
impl<T: Component + 'static> ComponentObject for T {
fn update_obj(&mut self, delta_time: f32) {
T::update(self, delta_time)
}
fn serialize_obj(&self) -> Vec<u8> {
T::serialize(self)
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct EntityImpl {
id: usize,
bitmask: u64,
// The key is the unique bit flag for the component type.
components: HashMap<u64, Box<dyn ComponentObject>>,
}
impl EntityImpl {
pub fn new(id: usize) -> Self {
EntityImpl {
id,
bitmask: 0,
components: HashMap::new(),
}
}
}
impl Entity for EntityImpl {
fn add_component<C: Component>(&mut self, component: C) {
let type_id = TypeId::of::<C>();
let mut registry = COMPONENT_REGISTRY.lock().unwrap();
let bit = registry.entry(type_id).or_insert_with(|| {
let mut next_bit = NEXT_COMPONENT_BIT.lock().unwrap();
let current = *next_bit;
*next_bit *= 2;
current
});
self.bitmask |= *bit;
self.components.insert(*bit, Box::new(component));
}
fn remove_component<C: Component>(&mut self) {
let type_id = TypeId::of::<C>();
if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) {
self.bitmask &= !bit;
}
}
fn get_component<C: Component>(&self) -> Option<&C> {
let type_id = TypeId::of::<C>();
if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) {
self.components.get(&bit)
.and_then(|boxed| boxed.as_any().downcast_ref::<C>())
} else {
None
}
}
fn serialize(&self) -> Vec<u8> {
// Serialize the entity's bitmask into 6 bytes (lowest 48 bits).
let mut bytes = self.bitmask.to_le_bytes().to_vec();
bytes.truncate(6);
bytes
}
fn deserialize(data: &[u8; 6]) -> Self {
let mut full = [0u8; 8];
full[..6].copy_from_slice(data);
let bitmask = u64::from_le_bytes(full);
// When deserializing, we recreate an entity with the restored bitmask.
// Note: The individual component data are not restored here.
Self {
id: 0,
bitmask,
components: HashMap::new(),
}
}
}
pub struct ECS {
next_entity_id: usize,
pub entities: HashMap<usize, EntityImpl>,
}
impl ECS {
pub fn new() -> Self {
ECS {
next_entity_id: 0,
entities: HashMap::new(),
}
}
pub fn create_entity(&mut self) -> &mut EntityImpl {
let entity = EntityImpl::new(self.next_entity_id);
self.entities.insert(self.next_entity_id, entity);
self.next_entity_id += 1;
self.entities.get_mut(&(self.next_entity_id - 1)).unwrap()
}
pub fn update(&mut self, delta_time: f32) {
for entity in self.entities.values_mut() {
// Update each component attached to the entity.
for comp in entity.components.values_mut() {
comp.update_obj(delta_time);
}
}
}
pub fn serialize(&self) -> Vec<u8> {
let mut data = Vec::new();
// For each entity, store its id (8 bytes) and its 6-byte bitmask.
for (id, entity) in &self.entities {
data.extend_from_slice(&id.to_le_bytes());
data.extend_from_slice(&entity.serialize());
}
data
}
pub fn deserialize(&mut self, data: &[u8]) {
self.entities.clear();
// Each serialized entity uses 8 (id) + 6 (bitmask) = 14 bytes.
let entity_size = 14;
let count = data.len() / entity_size;
for i in 0..count {
let offset = i * entity_size;
let mut id_bytes = [0u8; 8];
id_bytes.copy_from_slice(&data[offset..offset + 8]);
let id = usize::from_le_bytes(id_bytes);
let mut mask_bytes = [0u8; 6];
mask_bytes.copy_from_slice(&data[offset + 8..offset + 14]);
let entity = EntityImpl::deserialize(&mask_bytes);
self.entities.insert(id, entity);
}
self.next_entity_id = count;
}
}

View file

@ -3,8 +3,10 @@ use std::fmt::Write as FmtWrite;
use std::mem;
use backtrace::Backtrace;
use native_dialog::{MessageDialog, MessageType};
use parking_lot::Once;
use regex::Regex;
use tracing::error;
static INIT: parking_lot::Once = Once::new();
@ -48,7 +50,7 @@ fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box<dyn Err
let panic_msg = format!(
"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
We have generated a report file at \"{}\". Submit an issue or email with the subject of \"Zenyx Crash Report\" and include the report as an attachment.
We have generated a report file at '{}'. Submit an issue or email with the subject of 'Zenyx Crash Report' and include the report as an attachment.
To submit the crash report:
@ -62,6 +64,14 @@ Thank you kindly!", log_path.display());
"\nFor future reference, the error summary is as follows:\n{}",
payload_str.red().bold()
);
if let Err(e ) = MessageDialog::new()
.set_type(MessageType::Error)
.set_title(&format!("{}",payload_str))
// wont display properly if not debug formatted??
.set_text(&format!("{:#?}", panic_msg))
.show_alert() {
error!("{e}");
}
Ok(())
}

View file

@ -29,6 +29,7 @@ async fn main() -> anyhow::Result<()> {
info!("{}", "Debug mode disabled".bright_blue());
set_panic_hook();
}
set_panic_hook();
setup();
let repl_thread = std::thread::spawn(|| {