refactor: A new beginning

This commit is contained in:
Chance 2025-04-11 20:17:24 -04:00 committed by BitSyndicate
parent fc43a7454a
commit 6ce3e627cc
Signed by: bitsyndicate
GPG key ID: 443E4198D6BBA6DE
33 changed files with 21 additions and 7657 deletions

View file

@ -40,7 +40,7 @@ jobs:
sh get-docker.sh
# Start Docker daemon directly (instead of using service)
dockerd &
sudo dockerd &
# Give the Docker daemon a moment to initialize
sleep 10

64
.vscode/launch.json vendored
View file

@ -1,64 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'zenyx'",
"cargo": {
"args": [
"build",
"--bin=zenyx",
"--package=zenyx"
],
"filter": {
"name": "zenyx",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'zenyx'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=zenyx",
"--package=zenyx"
],
"filter": {
"name": "zenyx",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'zen_core'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=zen_core"
],
"filter": {
"name": "zen_core",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

3689
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,20 @@
[package]
name = "zenyx"
version = "0.1.0"
edition = "2024"
authors = ["Caznix (Chance) <Caznix01@gmail.com>"]
description = "A memory safe, opinionated Game Engine/Framework, written in Rust."
keywords = ["engine", "graphics", "game"]
categories = ["game-development", "graphics"]
license = "MIT"
homepage = "https://zenyx-engine.github.io/"
repository = "https://codeberg.org/Caznix/Zenyx"
[workspace]
resolver = "2"
members = ["engine","subcrates/zen_core"]
# members = ["engine","subcrates/zen_core"]
[workspace.dependencies]
parking_lot = "0.12.3"
zen_core = { path = "./subcrates/zen_core" }
[profile.release]
lto = true

View file

@ -1,22 +0,0 @@
# Blender 4.2.3 LTS MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 1
newmtl Material.003
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 1

BIN
Pumpkin.obj (Stored with Git LFS)

Binary file not shown.

View file

@ -1,42 +0,0 @@
[package]
name = "zenyx"
version = "0.1.0"
edition = "2024"
authors = ["Caznix (Chance) <Caznix01@gmail.com>"]
description = "A memory safe, opinionated Game Engine/Framework, written in Rust."
keywords = ["engine", "graphics", "game"]
categories = ["game-development", "graphics"]
license = "MIT"
homepage = "https://zenyx-engine.github.io/"
documentation = "https://zenyx-engine.github.io/docs"
repository = "https://codeberg.org/Caznix/Zenyx"
[dependencies]
colored = { version = "3.0.0", default-features = false }
parking_lot.workspace = true
rustyline = { version = "15.0.0", default-features = false, features = ["custom-bindings", "derive","with-file-history"] }
thiserror = { version = "2.0.11", default-features = false }
tokio = { version = "1.44.2", default-features = false, features = ["macros", "rt", "rt-multi-thread"] }
# Will be updated to 25.x.x when other dependencies are updated to be supported
wgpu = { version = "24.0.3", default-features = false }
winit = { version = "0.30.9", default-features = false, features = ["rwh_06", "wayland"] }
bytemuck = { version = "1.21.0", default-features = false }
futures = { version = "0.3.31", default-features = false, features = ["executor"] }
cgmath = { version = "0.18.0", default-features = false }
tracing = { version = "0.1.41", default-features = false }
tracing-subscriber = { version = "0.3.19", default-features = false, features = ["ansi", "fmt"] }
tobj = { version = "4.0.3", default-features = false }
ahash = { version = "0.8.11", default-features = false, features = ["std"] }
wgpu_text = { version = "0.9.2", default-features = false }
toml = { version = "0.8.20", default-features = false }
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
native-dialog = { version = "0.7.0", default-features = false }
sysinfo = { version = "0.34.2", default-features = false, features = ["system"] }
raw-cpuid = { version = "11.5.0", default-features = false }
image = { version = "0.25.6", default-features = false, features = ["png"] }
clap = { version = "4.5.35", default-features = false, features = ["std"] }
[build-dependencies]
built = { version = "0.7.7", default-features = false, features = ["cargo-lock", "chrono", "git2"] }
build-print = { version = "0.1.1", default-features = false }
cargo-lock = { version = "10.1.0", default-features = false }

View file

@ -1,6 +0,0 @@
[build]
pre-build = [
# "dpkg --add-architecture $CROSS_DEB_ARCH",
"apt-get update",
"apt-get --assume-yes install libwayland-dev:$CROSS_DEB_ARCH"
]

View file

@ -1,112 +0,0 @@
use build_print::*;
use cargo_lock::Lockfile;
use std::env;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::process::Command;
fn get_git_info() -> String {
match Command::new("git")
.arg("describe")
.arg("--always")
.arg("--dirty")
.output()
{
Ok(output) if output.status.success() => {
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
_ => {
match Command::new("git")
.arg("rev-parse")
.arg("--abbrev-ref")
.arg("HEAD")
.output()
{
Ok(output) if output.status.success() => {
let head = String::from_utf8_lossy(&output.stdout);
let head = head.trim();
if head == "HEAD" {
"DETACHED".to_string()
} else {
//TDDO: properly parse branch hashes
format!("BRANCH: {}", head)
}
}
_ => "UNKNOWN".to_string(),
}
}
}
}
static ALLOW_DEAD_CODE: &str = "#[allow(dead_code)]\n";
fn main() {
if let Err(e) = built::write_built_file() {
panic!("{e}");
}
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("built.rs");
let proj_root = env!("CARGO_MANIFEST_DIR").to_string();
let tmp = format!("{}/../Cargo.lock", proj_root);
let lockfile_path = Path::new(&tmp).canonicalize().expect("INvalid");
let cargo_version = match Command::new("cargo").arg("--version").output() {
Ok(output) if output.status.success() => {
let mut version = String::new();
version.push_str(ALLOW_DEAD_CODE);
version.push_str(&String::from_utf8_lossy(&output.stdout).trim().to_string());
version
}
_ => "unknown".to_string(),
};
info!("{cargo_version}");
let rustc_version = match Command::new("rustc").arg("--version").output() {
Ok(output) if output.status.success() => {
let mut version = String::new();
version.push_str(ALLOW_DEAD_CODE);
version.push_str(&String::from_utf8_lossy(&output.stdout).trim().to_string());
version
}
_ => "unknown".to_string(),
};
info!("{rustc_version}");
let git_info = get_git_info();
info!("Git Info: {}", git_info);
let mut built_rs = match OpenOptions::new().append(true).open(&dest_path) {
Ok(file) => file,
Err(e) => {
error!("Could not open built.rs for appending: {}", e);
return;
}
};
match Lockfile::load(lockfile_path) {
Ok(lockfile) => {
let dependencies_to_track = ["tokio", "winit", "wgpu"];
for package in lockfile.packages {
let name = package.name.as_str();
if dependencies_to_track.contains(&name) {
let version = package.version.to_string();
writeln!(
built_rs,
"{}pub static {}_VERSION: &str = \"{}\";",
ALLOW_DEAD_CODE,
name.to_uppercase().replace('-', "_"),
version
)
.unwrap();
}
}
}
Err(e) => {
error!("Error loading Cargo.lock: {}", e);
}
}
std::println!("cargo:rerun-if-changed=Cargo.lock");
std::println!("cargo:rerun-if-changed=.git/HEAD");
std::println!("cargo:rerun-if-changed=.git/index");
}

View file

@ -1,27 +0,0 @@
use clap::{Arg, Command};
#[derive(Debug)]
pub struct Cli {}
pub fn parse() {
let matches = Command::new("zenyx")
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::new("name")
.short('n')
.long("name")
.value_name("NAME")
.help("Sets a custom name"),
)
.subcommand(
Command::new("greet").about("Greets the given name").arg(
Arg::new("person")
.value_name("PERSON")
.help("The person to greet")
.required(true),
),
)
.get_matches();
}

View file

@ -1 +0,0 @@

View file

@ -1,22 +0,0 @@
use serde::{Deserialize, Serialize};
pub mod ecs;
pub mod panic;
pub mod repl;
pub mod splash;
pub mod render;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct EngineState {
log_level: LogLevel,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
enum LogLevel {
Info,
Debug,
Error,
Trace,
}
impl EngineState {}

View file

@ -1,225 +0,0 @@
use std::str::FromStr;
use std::{env, error::Error, path::PathBuf, thread};
use native_dialog::{MessageDialog, MessageType};
use parking_lot::Once;
use tracing::error;
static INIT: Once = Once::new();
pub fn set_panic_hook() {
INIT.call_once(|| {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if let Err(e) = process_panic(info) {
eprintln!("Error in panic hook: {}", e);
default_hook(info);
}
std::process::exit(1);
}));
});
}
fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box<dyn Error>> {
use std::io::Write;
use colored::Colorize;
let log_dir = PathBuf::from_str("./").expect("wtf, The current directory no longer exists?");
if !log_dir.exists() {
std::fs::create_dir_all(&log_dir)?;
}
let log_path = log_dir.join("panic.log");
let mut file = std::fs::File::create(&log_path)?;
let payload = info.payload();
let payload_str = if let Some(s) = payload.downcast_ref::<&str>() {
*s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<non-string panic payload>"
};
writeln!(file, "Panic Occurred: {}", payload_str)?;
if let Some(location) = info.location() {
writeln!(file, "Panic Location: {}", location)?;
}
writeln!(file, "{}", capture_backtrace().sanitize_path())?;
// Add more contextual information
writeln!(file, "\n--- Additional Information ---")?;
// Rust Version
if let Ok(rust_version) = rust_version() {
writeln!(file, "Rust Version: {}", rust_version)?;
}
// Command-line Arguments
writeln!(file, "Command-line Arguments:")?;
for arg in env::args() {
writeln!(file, " {}", arg)?;
}
// Environment Variables (consider filtering sensitive ones)
writeln!(file, "\nEnvironment Variables (selected):")?;
let interesting_env_vars = ["PATH", "RUST_VERSION", "CARGO_TARGET_DIR", "HOME", "USER"];
for (key, value) in env::vars() {
if interesting_env_vars.contains(&key.as_str()) {
writeln!(file, " {}: {}", key, value)?;
}
}
// Current Working Directory
if let Ok(cwd) = env::current_dir() {
writeln!(file, "\nCurrent Working Directory: {}", cwd.display())?;
}
// Thread Information
if let Some(thread) = thread::current().name() {
writeln!(file, "\nThread Name: {}", thread)?;
} else {
writeln!(file, "\nThread ID: {:?}", thread::current().id())?;
}
let panic_msg = format!(
r#"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
We have generated a detailed 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:
https://codeberg.org/Caznix/Zenyx/issues
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
Thank you kindly!"#,
log_path.display()
);
let final_msg = format!(
r#"{}
For future reference, the error summary is as follows:
{}
More details can be found in the crash report file."#,
panic_msg, payload_str
);
println!("{}", final_msg.red().bold());
if let Err(e) = MessageDialog::new()
.set_type(MessageType::Error)
.set_title("A fatal error in Zenyx has occurred")
.set_text(&final_msg)
.show_confirm()
{
error!("Failed to show message dialog: {e}")
}
Ok(())
}
fn rust_version() -> Result<String, Box<dyn Error>> {
let version = env!("CARGO_PKG_RUST_VERSION");
Ok(version.to_string())
}
fn capture_backtrace() -> String {
let mut backtrace = String::new();
let sysinfo = crate::metadata::SystemMetadata::current();
backtrace.push_str(&format!(
"--- System Information ---\n{}\n",
sysinfo.verbose_summary()
));
let trace = std::backtrace::Backtrace::force_capture();
let message = format!("\n--- Backtrace ---\n\n");
backtrace.push_str(&message);
backtrace.push_str(&format!("{trace:#}"));
backtrace
}
trait Sanitize {
fn sanitize_path(&self) -> String;
}
impl Sanitize for str {
fn sanitize_path(&self) -> String {
let prefixes = ["/home/", "/Users/", "\\Users\\", "/opt/home/"];
let mut result = String::from(self);
for prefix in prefixes {
if let Some(start_index) = result.find(prefix) {
let start_of_user = start_index + prefix.len();
let mut end_of_user = result[start_of_user..]
.find(|c| c == '/' || c == '\\')
.map(|i| start_of_user + i)
.unwrap_or(result.len());
if end_of_user == start_of_user && start_of_user < result.len() {
end_of_user = result.len();
}
result.replace_range(start_of_user..end_of_user, "<USER>");
break;
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_home() {
assert_eq!(
"/home/<USER>/documents",
"/home/john.doe/documents".sanitize_path()
);
assert_eq!("/home/<USER>", "/home/jane".sanitize_path());
assert_eq!("/opt/home/<USER>", "/opt/home/user".sanitize_path());
}
#[test]
fn test_sanitize_users_unix() {
assert_eq!(
"/Users/<USER>/desktop",
"/Users/alice/desktop".sanitize_path()
);
assert_eq!("/Users/<USER>/", "/Users/bob/".sanitize_path());
assert_eq!("/user/Users/<USER>", "/user/Users/name".sanitize_path());
}
#[test]
fn test_sanitize_users_windows() {
assert_eq!(
"\\Users\\<USER>\\documents",
"\\Users\\charlie\\documents".sanitize_path()
);
assert_eq!("\\Users\\<USER>", "\\Users\\david".sanitize_path());
assert_eq!(
"C:\\Other\\Users\\<USER>",
"C:\\Other\\Users\\folder".sanitize_path()
);
}
#[test]
fn test_no_match() {
assert_eq!("/opt/data/file.txt", "/opt/data/file.txt".sanitize_path());
}
#[test]
fn test_mixed_separators() {
assert_eq!(
"/home/<USER>\\documents",
"/home/eve\\documents".sanitize_path()
);
assert_eq!(
"\\Users\\<USER>/desktop",
"\\Users\\frank/desktop".sanitize_path()
);
}
}

View file

@ -1,749 +0,0 @@
use std::borrow::Cow;
use std::mem::offset_of;
use std::sync::Arc;
use std::time::Instant;
use cgmath::{Deg, Matrix4, Point3, Rad, SquareMatrix, Vector3, perspective};
use futures::executor::block_on;
use tracing::{debug, error, trace};
use wgpu::TextureUsages;
use wgpu::{Backends, InstanceDescriptor, util::DeviceExt};
use wgpu_text::glyph_brush::ab_glyph::FontRef;
use wgpu_text::glyph_brush::{HorizontalAlign, Layout, OwnedSection, OwnedText, VerticalAlign};
use wgpu_text::{BrushBuilder, TextBrush};
use winit::window::Window;
use crate::error::Result;
use crate::error::{ZenyxError, ZenyxErrorKind};
use super::TerminalState;
const SHADER_SRC: &str = include_str!("shader.wgsl");
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub position: [f32; 3],
pub normal: [f32; 3],
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] = [
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: offset_of!(Vertex, normal) as u64,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
];
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniform {
view: [[f32; 4]; 4],
proj: [[f32; 4]; 4],
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ModelUniform {
model: [[f32; 4]; 4],
}
struct Camera {
uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
view: Matrix4<f32>,
proj: Matrix4<f32>,
}
impl Camera {
fn new(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
width: u32,
height: u32,
) -> Self {
let view = Matrix4::look_at_rh(
Point3::new(0.0, 0.0, 3.0),
Point3::new(0.0, 0.0, 0.0),
Vector3::unit_y(),
);
let aspect = width as f32 / height as f32;
let proj = perspective(Rad::from(Deg(45.0)), aspect, 0.1, 100.0);
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Camera Uniform Buffer"),
size: std::mem::size_of::<CameraUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Camera Bind Group"),
layout: bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
Self {
uniform_buffer,
bind_group,
view,
proj,
}
}
fn resize(&mut self, width: u32, height: u32) {
let aspect = width as f32 / height as f32;
self.proj = perspective(Rad::from(Deg(45.0)), aspect, 0.1, 100.0);
}
fn update(&self, queue: &wgpu::Queue) {
let view_array: [[f32; 4]; 4] = self.view.into();
let proj_array: [[f32; 4]; 4] = self.proj.into();
let uniform = CameraUniform {
view: view_array,
proj: proj_array,
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
}
}
#[derive(Debug)]
struct Model {
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
index_count: u32,
transform: Matrix4<f32>,
version: u32,
}
impl Model {
fn new(
device: &wgpu::Device,
vertices: &[Vertex],
indices: &[u32],
bind_group_layout: &wgpu::BindGroupLayout,
) -> Self {
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX,
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Model Uniform Buffer"),
size: std::mem::size_of::<ModelUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Model Bind Group"),
layout: bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
Self {
vertex_buffer,
index_buffer,
uniform_buffer,
bind_group,
index_count: indices.len() as u32,
transform: Matrix4::identity(),
version: 1,
}
}
fn update(&self, queue: &wgpu::Queue) {
let model_array: [[f32; 4]; 4] = self.transform.into();
let uniform = ModelUniform { model: model_array };
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
}
fn set_transform(&mut self, transform: Matrix4<f32>) {
if self.transform != transform {
self.transform = transform;
self.version += 1;
}
}
}
pub struct Renderer<'window> {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'window>,
surface_config: wgpu::SurfaceConfiguration,
camera: Camera,
models: Vec<Model>,
render_pipeline: wgpu::RenderPipeline,
depth_texture: wgpu::Texture,
depth_texture_view: wgpu::TextureView,
camera_bind_group_layout: wgpu::BindGroupLayout,
model_bind_group_layout: wgpu::BindGroupLayout,
bg_color: wgpu::Color,
start_time: Instant,
last_frame_instant: Instant,
frame_count: u32,
fps: f32,
font_state: FontState,
model_versions: Vec<u32>,
}
struct FontState {
brush: TextBrush<FontRef<'static>>,
output_section: OwnedSection,
input_section: OwnedSection,
fps_section: OwnedSection,
scale: f32,
color: wgpu::Color,
}
impl<'window> Renderer<'window> {
pub async fn new(window: Arc<Window>) -> Result<Self> {
let instance = wgpu::Instance::new(&InstanceDescriptor {
backends: Backends::from_comma_list("dx12,metal,opengl,webgpu,vulkan"),
..Default::default()
});
let surface = instance.create_surface(Arc::clone(&window))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
..Default::default()
})
.await
.ok_or_else(|| {
ZenyxError::builder(ZenyxErrorKind::AdapterRequest)
.with_message("No suitable adapter found")
.build()
})?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor::default(), None)
.await
.map_err(ZenyxError::from)?;
let size = window.inner_size();
let width = size.width.max(1);
let height = size.height.max(1);
let camera_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Camera Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
std::mem::size_of::<CameraUniform>() as u64,
),
},
count: None,
}],
});
let model_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Model Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
std::mem::size_of::<ModelUniform>() as u64,
),
},
count: None,
}],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Pipeline Layout"),
bind_group_layouts: &[&camera_bind_group_layout, &model_bind_group_layout],
push_constant_ranges: &[],
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Main Shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(SHADER_SRC)),
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Main Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: surface.get_capabilities(&adapter).formats[0],
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
// cull_mode: ,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let camera = Camera::new(&device, &camera_bind_group_layout, width, height);
let surface_caps = surface.get_capabilities(&adapter);
let present_mode = [
wgpu::PresentMode::Immediate,
wgpu::PresentMode::Mailbox,
wgpu::PresentMode::AutoNoVsync,
]
.iter()
.copied()
.find(|mode| surface_caps.present_modes.contains(mode))
.unwrap_or(wgpu::PresentMode::Fifo);
debug!("Using {:#?} present mode.", present_mode);
let surface_config = wgpu::SurfaceConfiguration {
width,
height,
format: surface_caps.formats[0],
present_mode,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
usage: TextureUsages::RENDER_ATTACHMENT,
desired_maximum_frame_latency: 3,
};
surface.configure(&device, &surface_config);
let (depth_texture, depth_texture_view) =
create_depth_texture(&device, surface_config.width, surface_config.height);
let font_bytes = include_bytes!("DejaVuSans.ttf");
let font = FontRef::try_from_slice(font_bytes).map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::FontLoading)
.with_message("Font loading failed")
.with_source(e)
.build()
})?;
let brush =
BrushBuilder::using_font(font).build(&device, width, height, surface_config.format);
let base_width = 1280.0;
let base_scale = 30.0;
let scale = base_scale * (surface_config.width as f32 / base_width as f32).clamp(0.5, 2.0);
let color = wgpu::Color::WHITE;
let fps_section = OwnedSection::default()
.add_text(OwnedText::new("FPS: 0.00").with_scale(scale).with_color([
color.r as f32,
color.g as f32,
color.b as f32,
color.a as f32,
]))
.with_screen_position((10.0, 10.0))
.with_bounds((200.0, 50.0))
.with_layout(
Layout::default()
.h_align(HorizontalAlign::Left)
.v_align(VerticalAlign::Top),
);
let output_section = OwnedSection::default()
.with_screen_position((10.0, 50.0))
.with_bounds((width as f32 - 20.0, f32::MAX))
.with_layout(
Layout::default()
.h_align(HorizontalAlign::Left)
.v_align(VerticalAlign::Top),
);
let input_section = OwnedSection::default()
.with_screen_position((10.0, height as f32 - 50.0))
.with_bounds((width as f32 - 20.0, f32::MAX))
.with_layout(
Layout::default()
.h_align(HorizontalAlign::Left)
.v_align(VerticalAlign::Top),
);
Ok(Self {
device,
queue,
surface,
surface_config,
camera,
models: Vec::new(),
render_pipeline,
camera_bind_group_layout,
model_bind_group_layout,
bg_color: wgpu::Color {
r: 0.1,
g: 0.1,
b: 0.1,
a: 1.0,
},
start_time: Instant::now(),
last_frame_instant: Instant::now(),
frame_count: 0,
depth_texture,
depth_texture_view,
fps: 0f32,
font_state: FontState {
brush,
fps_section,
output_section,
input_section,
scale,
color,
},
model_versions: vec![],
})
}
pub fn new_blocking(window: Arc<Window>) -> Result<Self> {
block_on(Self::new(window))
}
pub fn add_model(&mut self, vertices: &[Vertex], indicies: &[u32]) {
let model = Model::new(
&self.device,
vertices,
indicies,
&self.model_bind_group_layout,
);
self.models.push(model);
self.model_versions.push(0);
}
pub fn resize(&mut self, new_size: (u32, u32)) {
let (width, height) = new_size;
let (depth_texture, depth_view) = create_depth_texture(&self.device, width, height);
self.surface_config.width = width.max(1);
self.surface_config.height = height.max(1);
self.surface.configure(&self.device, &self.surface_config);
self.depth_texture = depth_texture;
self.depth_texture_view = depth_view;
self.font_state
.brush
.resize_view(width as f32, height as f32, &self.queue);
self.font_state.output_section.bounds = (width as f32 - 20.0, height as f32 - 60.0);
self.font_state.input_section.screen_position = (10.0, height as f32 - 50.0);
self.camera.resize(width, height);
}
pub fn draw(&mut self, terminal_state: Option<&mut TerminalState>) {
let elapsed = self.start_time.elapsed().as_secs_f32();
if let Some(terminal_state) = terminal_state {
let delta_time = self.last_frame_instant.elapsed().as_secs_f32();
self.draw_terminal(terminal_state, delta_time);
} else {
self.camera.update(&self.queue);
for (i, model) in self.models.iter_mut().enumerate() {
let angle = Rad(elapsed * 0.8 + i as f32 * 0.3);
if i % 2 == 0 {
model.set_transform(Matrix4::from_angle_y(angle));
} else {
model
.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle));
}
}
for (i, model) in self.models.iter().enumerate() {
if model.version > self.model_versions[i] {
model.update(&self.queue);
#[cfg(debug_assertions)]
trace!("Updating model: {:#?}", model);
self.model_versions[i] = model.version;
}
}
}
let surface_texture = self
.surface
.get_current_texture()
.map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::SurfaceTexture)
.with_message("Failed to acquire surface texture")
.with_source(e)
.build()
})
.unwrap();
let view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
let fps_text = format!("FPS: {:.2}", self.fps);
self.font_state.fps_section.text.clear();
self.font_state.fps_section.text.push(
OwnedText::new(fps_text)
.with_scale(self.font_state.scale)
.with_color([
self.font_state.color.r as f32,
self.font_state.color.g as f32,
self.font_state.color.b as f32,
self.font_state.color.a as f32,
]),
);
if let Err(e) = self.font_state.brush.queue(
&self.device,
&self.queue,
&[
self.font_state.fps_section.clone(),
self.font_state.input_section.clone(),
self.font_state.output_section.clone(),
],
) {
error!("Failed to queue text: {}", e);
}
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Main Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.bg_color),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.camera.bind_group, &[]);
for model in &self.models {
render_pass.set_bind_group(1, &model.bind_group, &[]);
render_pass.set_vertex_buffer(0, model.vertex_buffer.slice(..));
render_pass
.set_index_buffer(model.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..model.index_count, 0, 0..1);
}
}
{
let mut text_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Text Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
self.font_state.brush.draw(&mut text_pass);
}
self.queue.submit(Some(encoder.finish()));
surface_texture.present();
self.frame_count += 1;
let elapsed_secs = self.last_frame_instant.elapsed().as_secs_f32();
if elapsed_secs >= 1.0 {
let fps = self.frame_count as f32 / elapsed_secs;
// trace!("Renderer FPS: {:.2}", fps);
self.fps = fps;
self.frame_count = 0;
self.last_frame_instant = Instant::now();
}
}
fn draw_terminal(&mut self, terminal_state: &mut TerminalState, delta_time: f32) {
terminal_state.cursor_blink_timer += delta_time;
if terminal_state.cursor_blink_timer >= 0.5 {
terminal_state.show_cursor = !terminal_state.show_cursor;
terminal_state.cursor_blink_timer = 0.0;
}
let line_height = self.font_state.scale * 1.5;
let max_visible_lines = (self.surface_config.height as f32 / line_height) as usize;
terminal_state.max_history_lines = max_visible_lines;
self.font_state.output_section.text.clear();
let mut current_line = 0;
let mut output_y = 0.0;
for line in terminal_state.output_history.iter().rev() {
let sublines = line.split('\n').collect::<Vec<_>>();
for subline in sublines.iter().rev() {
if current_line >= terminal_state.scroll_offset + max_visible_lines {
break;
}
if current_line >= terminal_state.scroll_offset {
let processed = subline.replace('\t', " ");
self.font_state.output_section.text.push(
OwnedText::new(processed)
.with_scale(self.font_state.scale)
.with_color([1.0, 1.0, 1.0, 1.0]),
);
output_y += line_height;
}
current_line += 1;
}
if current_line >= terminal_state.scroll_offset + max_visible_lines {
break;
}
}
self.font_state.input_section.text.clear();
let mut input_y = 0.0;
let input_text = format!(
"> {}{}",
terminal_state.input_buffer.replace('\t', " "),
if terminal_state.show_cursor { "_" } else { "" }
);
for (line_num, subline) in input_text.split('\n').enumerate() {
self.font_state.input_section.text.push(
OwnedText::new(subline)
.with_scale(self.font_state.scale)
.with_color([0.0, 1.0, 0.0, 1.0]), // .with_position((0.0, input_y)),
);
input_y += line_height;
if line_num >= 2 {
break;
}
}
self.font_state.output_section.bounds = (
self.surface_config.width as f32 - 20.0,
self.surface_config.height as f32 - 60.0,
);
self.font_state.input_section.bounds =
(self.surface_config.width as f32 - 20.0, line_height * 3.0);
if let Err(e) = self.font_state.brush.queue(
&self.device,
&self.queue,
&[
self.font_state.output_section.clone(),
self.font_state.input_section.clone(),
self.font_state.fps_section.clone(),
],
) {
error!("Failed to queue text: {}", e);
}
}
pub fn set_bg_color(&mut self, color: wgpu::Color) {
self.bg_color = color;
}
pub fn bg_color(&self) -> &wgpu::Color {
&self.bg_color
}
pub fn text_color(&self) -> &wgpu::Color {
&self.font_state.color
}
pub fn set_text_color(&mut self, color: wgpu::Color) {
self.font_state.color = color;
}
}
fn create_depth_texture(
device: &wgpu::Device,
width: u32,
height: u32,
) -> (wgpu::Texture, wgpu::TextureView) {
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let desc = wgpu::TextureDescriptor {
label: Some("Depth Texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
let texture = device.create_texture(&desc);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(texture, view)
}

View file

@ -1,582 +0,0 @@
use std::env;
use std::fs;
use std::io::Cursor;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
use ctx::{Renderer, Vertex};
use image::ImageDecoder;
use image::ImageFormat;
use tobj::{LoadOptions, Model};
use tracing::{debug, error, info, trace, warn};
use wgpu::rwh::HasWindowHandle;
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::dpi::Size;
use winit::event::{KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::NamedKey;
#[cfg(target_os = "windows")]
use winit::platform::windows::WindowAttributesExtWindows;
use winit::window::Fullscreen;
use winit::window::Icon;
use winit::window::Window;
use winit::window::WindowId;
use super::repl::input::evaluate_command;
pub mod ctx;
pub struct WindowContext<'window> {
window: Arc<Window>,
ctx: Renderer<'window>,
main_window: bool,
terminal_state: Option<TerminalState>,
}
impl Deref for WindowContext<'_> {
type Target = winit::window::Window;
fn deref(&self) -> &Self::Target {
self.window.as_ref()
}
}
impl WindowContext<'_> {
pub fn is_main_window(&self) -> bool {
self.main_window
}
}
#[derive(Default)]
pub struct TerminalState {
input_buffer: String,
output_history: Vec<String>,
scroll_offset: usize,
show_cursor: bool,
cursor_blink_timer: f32,
command_queue: Vec<String>,
needs_execution: bool,
max_history_lines: usize,
input_history: Vec<String>,
}
pub struct App<'window> {
windows: ahash::AHashMap<WindowId, WindowContext<'window>>,
// Number of windows that have been dropped but *NOT* destroyed yet
windows_in_limbo: usize,
}
impl Default for App<'_> {
fn default() -> Self {
Self {
windows: ahash::AHashMap::new(),
windows_in_limbo: 0,
}
}
}
static CUBE_OBJ: &str = "
# Blender 4.2.3 LTS
# www.blender.org
mtllib untitled.mtl
o Cube
v 0.645975 0.645975 -0.645975
v 0.645975 -0.645975 -0.645975
v 0.645975 0.645975 0.645975
v 0.645975 -0.645975 0.645975
v -0.645975 0.645975 -0.645975
v -0.645975 -0.645975 -0.645975
v -0.645975 0.645975 0.645975
v -0.645975 -0.645975 0.645975
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
s 0
usemtl Material
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
f 6/11/6 5/10/6 1/1/6 2/13/6
";
impl App<'_> {
const ICON: &'static [u8] =
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/Badge.png"));
fn create_main_window(&mut self, event_loop: &ActiveEventLoop) {
let icon = self.load_icon_from_bytes(Self::ICON).unwrap();
let win_attr = Window::default_attributes()
.with_title("Zenyx")
.with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0)))
.with_window_icon(icon.clone());
// .with_taskbar_icon(icon);
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
let obj = match tobj::load_obj(
"Pumpkin.obj",
&LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
) {
Ok(obj) => obj,
Err(e) => {
error!("Failed to load Pumpkin.obj: {e}");
let fallback_obj = CUBE_OBJ.to_string();
tobj::load_obj_buf(
&mut fallback_obj.as_bytes(),
&LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
},
|_| Ok(Default::default()),
)
.expect("Failed to load fallback CUBE_OBJ")
}
};
let (combined_vertices, combined_indices) = parse_obj(&obj.0);
wgpu_ctx.add_model(&combined_vertices, &combined_indices);
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: true,
terminal_state: None,
},
);
info!("Main window created: {:?}", window_id);
}
<<<<<<< HEAD
Err(e) => error!("Failed to create WGPU context: {:#}", e),
}
}
Err(e) => error!("Failed to create main window: {:#}", e),
=======
Err(e) => error!("Failed to create WGPU context: {:}", e),
}
}
Err(e) => error!("Failed to create main window: {}", e),
>>>>>>> refs/rewritten/main-6
}
}
fn create_terminal_window(&mut self, event_loop: &ActiveEventLoop) {
let icon = self.load_icon_from_bytes(Self::ICON).unwrap();
let win_attr = Window::default_attributes()
.with_title("Zenyx Terminal")
.with_inner_size(Size::Logical(LogicalSize::new(800.0, 600.0)))
.with_window_icon(icon);
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
wgpu_ctx.set_bg_color(wgpu::Color::BLACK);
wgpu_ctx.set_text_color(wgpu::Color::GREEN);
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: false,
terminal_state: Some(TerminalState::default()),
},
);
}
Err(e) => error!("Failed to create terminal WGPU context: {}", e),
}
}
Err(e) => error!("Failed to create terminal window: {}", e),
}
}
fn handle_terminal_input(&mut self, window_id: WindowId, key_event: KeyEvent) {
let Some(window_context) = self.windows.get_mut(&window_id) else {
return;
};
let state = window_context.terminal_state.as_mut().unwrap();
if key_event.state.is_pressed() {
match key_event.logical_key {
winit::keyboard::Key::Named(NamedKey::Enter) => {
if !state.input_buffer.is_empty() {
state.command_queue.push(state.input_buffer.clone());
state.input_history.push("\n\n".to_string());
state.input_buffer.clear();
state.needs_execution = true;
}
}
winit::keyboard::Key::Named(NamedKey::Backspace) => {
state.input_buffer.pop();
}
winit::keyboard::Key::Named(NamedKey::Space) => {
state.input_buffer.push(' ');
}
winit::keyboard::Key::Character(c) => {
state.input_buffer.push_str(&c);
}
_ => {}
}
}
}
fn handle_close_requested(&mut self, window_id: WindowId) {
if let Some(window) = self.windows.remove(&window_id) {
debug!("Window {:?} closed", window_id);
self.windows_in_limbo += 1;
if window.is_main_window() {
self.windows_in_limbo += self.windows.len();
self.windows.clear();
}
} else {
warn!("Tried to close non-existent window {:?}", window_id);
}
}
fn handle_keyboard_input(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
key_event: KeyEvent,
) {
if !key_event.state.is_pressed() || key_event.repeat {
return;
}
if let Some(window_context) = self.windows.get(&window_id) {
if window_context.terminal_state.is_some() {
self.handle_terminal_input(window_id, key_event);
return;
}
}
match key_event.physical_key {
winit::keyboard::PhysicalKey::Code(code) => match code {
winit::keyboard::KeyCode::Space => {
self.toggle_background(window_id);
}
winit::keyboard::KeyCode::Escape => {
self.spawn_child_window(event_loop);
}
winit::keyboard::KeyCode::F11 => self.toggle_fullscreen(window_id),
winit::keyboard::KeyCode::F12 => self.create_terminal_window(event_loop),
other => error!("Unimplemented keycode: {:?}", other),
},
_ => error!("Unhandled key event: {:?}", key_event),
}
}
fn toggle_background(&mut self, window_id: WindowId) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
let current_color = window_context.ctx.bg_color();
let new_color = match current_color {
&wgpu::Color::WHITE => wgpu::Color::BLACK,
&wgpu::Color::BLACK => wgpu::Color::WHITE,
_ => wgpu::Color::WHITE,
};
let new_text_color = match window_context.ctx.text_color() {
&wgpu::Color::WHITE => wgpu::Color::BLACK,
&wgpu::Color::BLACK => wgpu::Color::WHITE,
_ => wgpu::Color::WHITE,
};
println!("new text color {new_text_color:#?}");
window_context.ctx.set_bg_color(new_color);
window_context.ctx.set_text_color(new_text_color);
debug!("Toggled background color for window {:?}", window_id);
} else {
warn!("No window context for toggling background: {:?}", window_id);
}
}
fn toggle_fullscreen(&mut self, window_id: WindowId) {
if let Some(ctx) = self.windows.get_mut(&window_id) {
let is_fullscreen = ctx.window.fullscreen().is_some();
let fullscreen_mode = if is_fullscreen {
None
} else {
ctx.window
.current_monitor()
.map(|monitor| Fullscreen::Borderless(Some(monitor)))
};
ctx.window.set_fullscreen(fullscreen_mode);
debug!("Fullscreen toggled for window: {:?}", window_id);
} else {
warn!("No window found for fullscreen toggle: {:?}", window_id);
}
}
fn load_icon_from_bytes(&self, bytes: &[u8]) -> Result<Option<Icon>, String> {
let cursor = Cursor::new(bytes);
let format = image::guess_format(bytes).map_err(|_| "Failed to guess image format")?;
let decoder = match format {
ImageFormat::Png => image::codecs::png::PngDecoder::new(cursor)
.map_err(|e| format!("Failed to decode PNG: {}", e))?,
_ => {
let img = image::load_from_memory(bytes)
.map_err(|e| format!("Failed to load image: {}", e))?
.into_rgba8();
let (width, height) = img.dimensions();
return Icon::from_rgba(img.into_raw(), width, height)
.map(Some)
.map_err(|e| format!("Failed to create icon from bytes: {}", e));
}
};
let (width, height) = decoder.dimensions();
let mut image_data = vec![0; decoder.total_bytes() as usize];
decoder
.read_image(&mut image_data)
.map_err(|e| format!("Failed to read image data: {}", e))?;
Icon::from_rgba(image_data, width, height)
.map(Some)
.map_err(|e| format!("Failed to create icon from bytes: {}", e))
}
fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) {
if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) {
let title = format!("Zenyx - New Window {}", self.windows.len());
let icon = self.load_icon_from_bytes(Self::ICON).unwrap();
let win_attr = unsafe {
let base = Window::default_attributes()
.with_title(title)
// .with_taskbar_icon(icon)
.with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0)))
.with_window_icon(icon.clone());
match main_ctx.window_handle() {
Ok(handle) => {
if !cfg!(target_os = "windows") {
base.with_parent_window(Some(handle.as_raw()))
} else {
base
}
}
Err(e) => {
error!("{e}");
base
}
}
};
match event_loop.create_window(win_attr) {
Ok(window) => {
let window = Arc::new(window);
let window_id = window.id();
match Renderer::new_blocking(window.clone()) {
Ok(mut wgpu_ctx) => {
{
let mut tmp_path: PathBuf = env::temp_dir();
tmp_path.push("cube.obj");
if let Err(e) = fs::write(&tmp_path, CUBE_OBJ) {
error!("Failed to write cube OBJ to temp: {}", e);
}
let load_options = tobj::LoadOptions {
triangulate: true,
single_index: true,
..Default::default()
};
match tobj::load_obj(tmp_path.to_str().unwrap(), &load_options) {
Ok(cube_model) => {
let (cube_vertices, cube_indices) =
parse_obj(&cube_model.0);
wgpu_ctx.add_model(&cube_vertices, &cube_indices);
}
Err(e) => {
error!("Failed to load cube OBJ from temp file: {:#}", e)
}
}
}
self.windows.insert(
window_id,
WindowContext {
window,
ctx: wgpu_ctx,
main_window: false,
terminal_state: None,
},
);
debug!("Spawned new child window: {:?}", window_id);
}
Err(e) => error!("Failed to create WGPU context for child window: {}", e),
}
}
Err(e) => error!("Failed to create child window: {}", e),
}
} else {
error!("No main window found. Cannot spawn a child window.");
}
}
fn handle_redraw_requested(&mut self, window_id: WindowId) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
if let Some(terminal_state) = window_context.terminal_state.as_mut() {
if terminal_state.needs_execution {
for command in terminal_state.command_queue.drain(..) {
match evaluate_command(&command) {
Ok(output) => {
for line in output.lines() {
terminal_state.output_history.push(line.to_string());
}
}
Err(e) => {
terminal_state.output_history.push(format!("Error: {}", e));
}
}
}
terminal_state.needs_execution = false;
terminal_state.scroll_offset = terminal_state
.output_history
.len()
.saturating_sub(terminal_state.max_history_lines);
}
}
let terminal_state = window_context.terminal_state.as_mut();
window_context.ctx.draw(terminal_state);
window_context.request_redraw();
}
}
fn handle_resize(&mut self, window_id: WindowId, new_size: winit::dpi::PhysicalSize<u32>) {
if let Some(window_context) = self.windows.get_mut(&window_id) {
// if we dont ignore size 0 this WILL cause a crash. DO NOT REMOVE
if new_size.height == 0 || new_size.width == 0 {
error!("Attempted to resize a window to 0x0!");
return;
}
window_context.ctx.resize(new_size.into());
window_context.window.request_redraw();
debug!(
"Resized window {:?} to {}x{}",
window_id, new_size.width, new_size.height
);
} else {
warn!("Received resize for unknown window {:?}", window_id);
}
}
fn handle_destroyed(&mut self, event_loop: &ActiveEventLoop) {
self.windows_in_limbo -= 1;
if self.windows_in_limbo == 0 && self.windows.is_empty() {
debug!("All windows are closed. Exiting event loop.");
event_loop.exit();
}
}
}
fn parse_obj(obj: &Vec<Model>) -> (Vec<Vertex>, Vec<u32>) {
let mut combined_vertices = Vec::new();
let mut combined_indices = Vec::new();
let mut vertex_offset = 0;
for object in obj {
let mesh: &_ = &object.mesh;
let vertices: Vec<Vertex> = (0..mesh.positions.len() / 3)
.map(|i| Vertex {
position: [
mesh.positions[i * 3],
mesh.positions[i * 3 + 1],
mesh.positions[i * 3 + 2],
],
normal: if !mesh.normals.is_empty() {
[
mesh.normals[i * 3],
mesh.normals[i * 3 + 1],
mesh.normals[i * 3 + 2],
]
} else {
[0.0; 3]
},
})
.collect();
combined_vertices.extend(vertices);
combined_indices.extend(mesh.indices.iter().map(|&index| index + vertex_offset));
vertex_offset += (mesh.positions.len() as u32) / 3;
}
(combined_vertices, combined_indices)
}
impl ApplicationHandler for App<'_> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.windows.is_empty() {
self.create_main_window(event_loop);
}
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.handle_close_requested(window_id);
}
WindowEvent::KeyboardInput {
event: key_event, ..
} => {
self.handle_keyboard_input(event_loop, window_id, key_event);
}
WindowEvent::RedrawRequested => {
self.handle_redraw_requested(window_id);
}
WindowEvent::Resized(new_size) => {
self.handle_resize(window_id, new_size);
}
WindowEvent::Destroyed => {
self.handle_destroyed(event_loop);
}
_ => trace!("Unhandled window event for window {:?}", window_id),
}
}
}
pub fn init_renderer(event_loop: EventLoop<()>) {
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::default();
if let Err(e) = event_loop.run_app(&mut app) {
error!("Failed to run application: {}", e);
}
}

View file

@ -1,42 +0,0 @@
struct CameraUniform {
view: mat4x4<f32>,
proj: mat4x4<f32>,
};
struct ModelUniform {
model: mat4x4<f32>,
};
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
@group(1) @binding(0)
var<uniform> model: ModelUniform;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) normal: vec3<f32>,
};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let model_pos = model.model * vec4<f32>(input.position, 1.0);
output.clip_position = camera.proj * camera.view * model_pos;
output.normal = input.normal;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let ambient: f32 = 0.2;
let light_dir = normalize(vec3<f32>(0.5, 1.0, 0.5));
let diffuse = clamp(dot(normalize(input.normal), light_dir), 0.0, 1.0);
let brightness = ambient + (1.0 - ambient) * diffuse;
return vec4<f32>(0.7 * brightness, 0.7 * brightness, 0.9 * brightness, 1.0);
}

View file

@ -1,410 +0,0 @@
use std::{collections::VecDeque, fs, path::PathBuf, str::FromStr};
use parking_lot::RwLock;
use super::{handler::Command, input::tokenize};
use crate::core::repl::handler::COMMAND_MANAGER;
use crate::error::{ZenyxError, ZenyxErrorKind};
#[derive(Default)]
pub struct HelpCommand;
impl Command for HelpCommand {
fn execute(&self, _args: Option<Vec<String>>) -> Result<String, ZenyxError> {
let manager = COMMAND_MANAGER.read();
let mut output = String::new();
output.push_str("Available commands:\n\n");
for (_, command) in manager.get_commands() {
output.push_str(&format!(
"Command: {}\n\tDescription: {}\n\tParameters: {}\n\tHelp: {}\n\n",
command.get_name().to_lowercase(),
command.get_description(),
command.get_params(),
command.get_help()
));
}
if !manager.aliases.is_empty() {
output.push_str("Aliases:\n");
for (alias, command) in &manager.aliases {
output.push_str(&format!("\t{} -> {}\n", alias, command));
}
}
Ok(output)
}
fn undo(&self) {}
fn redo(&self) {}
fn get_description(&self) -> String {
String::from("help")
}
fn get_help(&self) -> String {
String::from("Displays a list of available commands and their descriptions.")
}
fn get_params(&self) -> String {
String::from("No parameters required.")
}
fn get_name(&self) -> String {
String::from("Help")
}
}
#[derive(Default)]
pub struct ClearCommand;
impl Command for ClearCommand {
fn execute(&self, _args: Option<Vec<String>>) -> Result<String, ZenyxError> {
let _result = if cfg!(target_os = "windows") {
std::process::Command::new("cmd")
.args(["/c", "cls"])
.spawn()
} else {
std::process::Command::new("clear").spawn()
};
Ok(String::from("Screen cleared."))
}
fn undo(&self) {}
fn redo(&self) {}
fn get_description(&self) -> String {
String::from("A simple command that clears the terminal")
}
fn get_name(&self) -> String {
String::from("clear")
}
fn get_help(&self) -> String {
String::from("Clears the terminal")
}
fn get_params(&self) -> String {
String::from("None")
}
}
#[derive(Default)]
pub struct ExitCommand;
impl Command for ExitCommand {
fn execute(&self, args: Option<Vec<String>>) -> Result<String, ZenyxError> {
match args {
Some(args) => {
let exit_code = args[0].parse().map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Failed to parse exit code")
.with_source(e)
.build()
})?;
std::process::exit(exit_code);
}
None => {
std::process::exit(0);
}
}
}
fn undo(&self) {
todo!()
}
fn redo(&self) {
todo!()
}
fn get_description(&self) -> String {
String::from("Gracefully exists the program")
}
fn get_name(&self) -> String {
String::from("exit")
}
fn get_help(&self) -> String {
String::from("Exits, probably")
}
fn get_params(&self) -> String {
String::from("None")
}
}
#[derive(Default)]
pub struct ExecFile;
impl Command for ExecFile {
fn execute(&self, args: Option<Vec<String>>) -> Result<String, ZenyxError> {
match args {
Some(args) => {
let file_path = PathBuf::from_str(&args[0]).map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Invalid file path")
.with_source(e)
.build()
})?;
if file_path.extension().is_some() && file_path.extension().unwrap() != "zensh" {
Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Selected file was not a zensh file")
.build())
} else {
let mut script_output = String::new();
match self.evaluate_script_heap_based(file_path) {
Ok(commands_to_execute) => {
for (cmd_name, cmd_args) in commands_to_execute {
match COMMAND_MANAGER.read().execute(&cmd_name, cmd_args) {
Ok(output) => script_output.push_str(&output),
Err(e) => {
return Err(ZenyxError::builder(
ZenyxErrorKind::CommandExecution,
)
.with_message(format!(
"Error executing command '{}' in script: {}",
cmd_name, e
))
.build());
}
}
}
Ok(script_output)
}
Err(e) => Err(e),
}
}
}
None => Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Not enough arguments")
.build()),
}
}
fn undo(&self) {}
fn redo(&self) {}
fn get_description(&self) -> String {
String::from("Executes a file path")
}
fn get_name(&self) -> String {
String::from("exec")
}
fn get_help(&self) -> String {
String::from("this will read the contents of a .zensh file, evaluate it, and run its input")
}
fn get_params(&self) -> String {
String::from("1: File path")
}
}
impl ExecFile {
const MAX_RECURSION_DEPTH: usize = 100; // Increased for heap-based approach
fn evaluate_script_heap_based(
&self,
initial_file_path: PathBuf,
) -> Result<Vec<(String, Option<Vec<String>>)>, ZenyxError> {
let mut command_queue: VecDeque<(PathBuf, usize)> = VecDeque::new();
let mut collected_commands = Vec::new();
command_queue.push_back((initial_file_path, 0));
while let Some((current_file_path, current_depth)) = command_queue.pop_front() {
if current_depth > Self::MAX_RECURSION_DEPTH {
return Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution)
.with_message(format!(
"Recursion limit of {} exceeded while executing script.",
Self::MAX_RECURSION_DEPTH
))
.build());
}
let zscript_result = fs::read_to_string(&current_file_path).map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::Io)
.with_message(format!("Failed to read file: {}", current_file_path.display()))
.with_source(e)
.build()
});
match zscript_result {
Ok(zscript) => {
let commands: Vec<&str> = zscript.split(|c| c == ';' || c == '\n').collect();
for command_str in commands {
let command_str = command_str.trim();
if command_str.is_empty() {
continue;
}
let tokens = tokenize(command_str);
if tokens.is_empty() {
continue;
}
let cmd_name = &tokens[0];
let args: Option<Vec<String>> = if tokens.len() > 1 {
Some(tokens[1..].iter().map(|s| s.to_string()).collect())
} else {
None
};
if cmd_name.to_lowercase() == "exec" {
if let Some(ref file_args) = args {
if let Some(nested_file_path_str) = file_args.first() {
let nested_file_path =
PathBuf::from_str(nested_file_path_str).map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Invalid file path in nested exec command")
.with_source(e)
.build()
})?;
if nested_file_path.extension().is_some()
&& nested_file_path.extension().unwrap() != "zensh"
{
return Err(ZenyxError::builder(
ZenyxErrorKind::CommandParsing,
)
.with_message("Nested exec file was not a zensh file")
.build());
}
command_queue.push_back((nested_file_path, current_depth + 1));
} else {
return Err(ZenyxError::builder(
ZenyxErrorKind::CommandParsing,
)
.with_message("Not enough arguments for nested exec command")
.build());
}
} else {
return Err(ZenyxError::builder(
ZenyxErrorKind::CommandParsing,
)
.with_message("Not enough arguments for nested exec command")
.build());
}
} else {
collected_commands.push((cmd_name.to_owned(), args));
}
}
}
Err(e) => return Err(e),
}
}
Ok(collected_commands)
}
}
#[derive(Default)]
pub struct CounterCommand {
counter: RwLock<u32>,
}
impl Command for CounterCommand {
fn execute(&self, _args: Option<Vec<String>>) -> Result<String, ZenyxError> {
let mut count = self.counter.write();
*count += 1;
Ok(format!(
"CounterCommand executed. Current count: {}",
*count
))
}
fn undo(&self) {
println!("Undo CounterCommand.");
}
fn redo(&self) {
println!("Redo CounterCommand.");
}
fn get_description(&self) -> String {
String::from("counter")
}
fn get_help(&self) -> String {
String::from("Increments a counter every time it's executed.")
}
fn get_params(&self) -> String {
String::from("No parameters for CounterCommand.")
}
fn get_name(&self) -> String {
String::from("count")
}
}
#[derive(Default)]
pub struct PanicCommmand;
impl Command for PanicCommmand {
fn execute(&self, args: Option<Vec<String>>) -> Result<String, ZenyxError> {
if let Some(args) = args {
let panic_msg = &args[0];
panic!("{}", panic_msg);
} else {
let option: Option<i32> = None;
println!("Unwrapping None: {}", option.unwrap());
panic!("Panic command was called");
}
}
fn undo(&self) {}
fn redo(&self) {}
fn get_description(&self) -> String {
String::from("causes a panic with your provided message")
}
fn get_name(&self) -> String {
String::from("panic")
}
fn get_help(&self) -> String {
String::from("")
}
fn get_params(&self) -> String {
String::from("optional: panic msg")
}
}
fn eval(input: String) -> Result<Vec<(String, Option<Vec<String>>)>, ZenyxError> {
if input.trim().is_empty() {
return Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Input was empty")
.build());
}
let commands: Vec<&str> = input.split(|c| c == ';' || c == '\n').collect();
let mut evaluted = vec![];
for command in commands {
let command = command.trim();
if command.is_empty() {
println!("Empty command, skipping.");
continue;
}
let tokens = tokenize(command);
if tokens.is_empty() {
println!("Empty command, skipping.");
continue;
}
let cmd_name = &tokens[0];
let args: Option<Vec<String>> = if tokens.len() > 1 {
Some(tokens[1..].iter().map(|s| s.to_string()).collect())
} else {
None
};
evaluted.push((cmd_name.to_owned(), args));
}
Ok(evaluted)
}

View file

@ -1,166 +0,0 @@
use std::sync::LazyLock;
use ahash::AHashMap;
use colored::Colorize;
use parking_lot::RwLock;
use crate::error::{ZenyxError, ZenyxErrorKind};
pub static COMMAND_MANAGER: LazyLock<RwLock<CommandManager>> =
LazyLock::new(|| RwLock::new(CommandManager::init()));
#[macro_export]
macro_rules! commands {
[$($command:ty),*] => [
$(
{
let mut manager = $crate::core::repl::handler::COMMAND_MANAGER.write();
manager.add_command(Box::new(<$command>::default()));
}
)*
];
}
#[macro_export]
macro_rules! alias {
($($alias:expr => $command:expr),*) => {
$(
{
let mut manager = $crate::COMMAND_MANAGER.write();
manager.add_alias($alias, $command);
}
)*
};
}
fn hamming_distance(a: &str, b: &str) -> Option<usize> {
if a.len() != b.len() {
return None;
}
Some(
a.chars()
.zip(b.chars())
.filter(|(char_a, char_b)| char_a != char_b)
.count(),
)
}
fn edit_distance(a: &str, b: &str) -> usize {
let m = a.len();
let n = b.len();
let mut dp = vec![vec![0; n + 1]; m + 1];
for i in 0..=m {
for j in 0..=n {
if i == 0 {
dp[i][j] = j;
} else if j == 0 {
dp[i][j] = i;
} else if a.chars().nth(i - 1) == b.chars().nth(j - 1) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1]);
}
}
}
dp[m][n]
}
fn check_similarity(target: &str) -> Option<String> {
let max_hamming_distance: usize = 2;
let max_edit_distance: usize = 2;
let mut best_match: Option<String> = None;
let mut best_distance = usize::MAX;
for (cmd_name, _) in COMMAND_MANAGER.read().get_commands() {
if let Some(hamming_dist) = hamming_distance(target, cmd_name) {
if hamming_dist <= max_hamming_distance && hamming_dist < best_distance {
best_distance = hamming_dist;
best_match = Some(String::from(cmd_name));
}
} else {
let edit_dist = edit_distance(target, cmd_name);
if edit_dist <= max_edit_distance && edit_dist < best_distance {
best_distance = edit_dist;
best_match = Some(String::from(cmd_name));
}
}
}
best_match
}
pub struct CommandManager {
pub commands: AHashMap<String, Box<dyn Command>>,
pub aliases: AHashMap<String, String>,
}
impl CommandManager {
pub fn init() -> CommandManager {
CommandManager {
commands: AHashMap::new(),
aliases: AHashMap::new(),
}
}
pub fn get_commands(&self) -> std::collections::hash_map::Iter<'_, String, Box<dyn Command>> {
self.commands.iter()
}
pub fn execute_command(
&self,
command: &str,
args: Option<Vec<String>>,
) -> Result<String, ZenyxError> {
if let Some(command) = self.commands.get(command) {
let output = command.execute(args)?;
Ok(output)
} else {
let corrected_cmd = check_similarity(command);
if let Some(corrected_cmd) = corrected_cmd {
println!(
"Command: {} was not found. Did you mean {}?",
command.red().bold(),
corrected_cmd.green().bold().italic()
);
}
Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution)
.with_message(format!("Command '{}' not found.", command))
.build())
}
}
pub fn execute(&self, command: &str, args: Option<Vec<String>>) -> Result<String, ZenyxError> {
match self.aliases.get(command) {
Some(command) => self.execute(command, args),
None => {
let output = self.execute_command(command, args)?;
Ok(output)
}
}
}
pub fn add_command(&mut self, command: Box<dyn Command>) {
self.commands
.insert(command.get_name().to_lowercase(), command);
}
pub fn add_alias(&mut self, alias: &str, command: &str) {
self.aliases.insert(
alias.to_string().to_lowercase(),
command.to_string().to_lowercase(),
);
}
}
pub trait Command: Send + Sync {
fn execute(&self, args: Option<Vec<String>>) -> Result<String, ZenyxError>;
fn undo(&self);
fn redo(&self);
fn get_description(&self) -> String;
fn get_name(&self) -> String;
fn get_help(&self) -> String;
fn get_params(&self) -> String;
}

View file

@ -1,240 +0,0 @@
use std::{
borrow::Cow::{self, Borrowed, Owned},
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use colored::Colorize;
use parking_lot::Mutex;
use rustyline::{
Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, Helper,
Hinter, KeyEvent, RepeatCount, Validator, completion::Completer, error::ReadlineError,
highlight::Highlighter, hint::HistoryHinter, history::DefaultHistory,
};
#[allow(unused_imports)]
use tracing::{debug, error, info, warn};
use super::handler::COMMAND_MANAGER;
use crate::error::{Result, ZenyxError, ZenyxErrorKind};
#[derive(Default)]
struct CommandCompleter;
impl Completer for CommandCompleter {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let binding = COMMAND_MANAGER.read();
let binding = binding.get_commands();
let filtered_commands: Vec<_> = binding
.filter(|(command, _)| command.starts_with(line))
.collect();
let completions: Vec<String> = filtered_commands
.iter()
.filter(|(command, _)| command.starts_with(&line[..pos]))
.map(|(command, _)| command[pos..].to_string())
.collect();
println!("{:#?}", completions);
Ok((pos, completions))
}
}
#[derive(Completer, Helper, Hinter, Validator)]
struct MyHelper {
#[rustyline(Hinter)]
hinter: HistoryHinter,
#[rustyline(Completer)]
completer: CommandCompleter,
}
impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
if default {
Owned(prompt.bright_black().bold().to_string())
} else {
Borrowed(prompt)
}
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned(hint.italic().bright_black().to_string())
}
}
#[derive(Clone)]
struct BacktickEventHandler {
toggle_state: Arc<Mutex<bool>>, // Tracks whether logging is enabled or disabled
}
impl ConditionalEventHandler for BacktickEventHandler {
fn handle(&self, evt: &Event, _: RepeatCount, _: bool, _: &EventContext) -> Option<Cmd> {
if let Some(k) = evt.get(0) {
if *k == KeyEvent::from('`') {
let mut state = self.toggle_state.lock();
println!(
"Stdout Logging: {}",
if *state { "ON".green() } else { "OFF".red() }
);
if *state {
// LOGGER.write_to_stdout();
} else {
// LOGGER.write_to_file("z.log");
}
*state = !*state;
Some(Cmd::Noop)
} else {
None
}
} else {
unreachable!()
}
}
}
pub fn tokenize(command: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current_token = String::new();
let mut inside_string = false;
for char in command.chars() {
if char == '"' || char == '\'' {
inside_string = !inside_string;
} else if char.is_whitespace() && !inside_string {
if !current_token.is_empty() {
tokens.push(current_token);
current_token = String::new();
}
} else {
current_token.push(char);
}
}
// ignore the last token if it's empty. Who are we. Mojang? - Caz
if !current_token.is_empty() {
tokens.push(current_token);
}
tokens
}
pub fn parse_command(input: &str) -> Result<Vec<String>> {
let commands = input
.split(|c| c == ';' || c == '\n')
.map(|slice| slice.to_string())
.collect::<Vec<String>>();
Ok(commands)
}
pub fn evaluate_command(input: &str) -> std::result::Result<String, ZenyxError> {
if input.trim().is_empty() {
let err = ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Input was empty")
.build();
return Err(err);
}
let commands = input
.split(|c| c == ';' || c == '\n')
.map(|slice| slice.to_string())
.collect::<Vec<String>>();
let mut output = String::new();
for command in commands {
let command = command.trim();
if command.is_empty() {
error!("Empty command, skipping.");
continue;
}
let tokens = tokenize(command);
if tokens.is_empty() {
error!("Empty command, skipping.");
continue;
}
let cmd_name = &tokens[0];
let args: Option<Vec<String>> = if tokens.len() > 1 {
Some(tokens[1..].iter().map(|s| s.to_string()).collect())
} else {
None
};
match COMMAND_MANAGER.read().execute(cmd_name, args) {
Ok(command_output) => output.push_str(&command_output),
Err(e) => {
return Err(e);
}
}
}
Ok(output)
}
pub fn format_time() -> String {
let now = SystemTime::now();
let duration = now.duration_since(UNIX_EPOCH).unwrap();
let total_seconds = duration.as_secs();
let nanos = duration.subsec_nanos();
let milliseconds = nanos / 1_000_000;
let seconds_since_midnight_utc = total_seconds % (24 * 3600);
let hour = (seconds_since_midnight_utc / 3600) % 24;
let minute = (seconds_since_midnight_utc / 60) % 60;
let second = seconds_since_midnight_utc % 60;
format!(
"{:02}:{:02}:{:02}.{:03}",
hour, minute, second, milliseconds
)
}
pub async fn handle_repl() -> Result<()> {
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
rl.set_helper(Some(MyHelper {
hinter: HistoryHinter::new(),
completer: CommandCompleter::default(),
}));
rl.bind_sequence(
KeyEvent::from('`'),
EventHandler::Conditional(Box::new(BacktickEventHandler {
toggle_state: Arc::new(Mutex::new(false)),
})),
);
if rl.load_history("history.txt").is_err() {
debug!("No previous history.");
}
loop {
let time = format_time();
let prompt = format!("[{}/{}] {}", time, "SHELL", ">>\t");
let sig = rl.readline(&prompt.bright_white());
match sig {
Ok(line) => {
rl.add_history_entry(line.as_str())?;
match evaluate_command(line.as_str()) {
Ok(_) => continue,
Err(e) => error!("{e}"),
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL+C received, exiting...");
std::process::exit(0);
}
Err(ReadlineError::Eof) => {
println!("Error: CTRL+D pressed. Exiting...");
std::process::exit(0);
}
Err(err) => {
println!("Error: {}", err);
}
}
}
}

View file

@ -1,18 +0,0 @@
use commands::{ClearCommand, CounterCommand, ExecFile, ExitCommand, HelpCommand, PanicCommmand};
use crate::commands;
pub mod commands;
pub mod handler;
pub mod input;
pub fn setup() {
commands!(
HelpCommand,
ExecFile,
ClearCommand,
ExitCommand,
CounterCommand,
PanicCommmand
);
}

View file

@ -1,29 +0,0 @@
use colored::Colorize;
pub fn print_splash() {
println!(
"{}",
format!(
r#"
Version: {}
"#,
env!("CARGO_PKG_VERSION").green()
)
.bright_yellow()
);
}

View file

@ -1,259 +0,0 @@
use std::fmt::Write;
use colored::Colorize;
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum ZenyxErrorKind {
#[error("Surface creation failed")]
SurfaceCreation,
#[error("Surface configuration failed")]
SurfaceConfiguration,
#[error("Adapter request failed")]
AdapterRequest,
#[error("Device request failed")]
DeviceRequest,
#[error("Surface texture acquisition failed")]
SurfaceTexture,
#[error("Command parsing failed")]
CommandParsing,
#[error("Command execution failed")]
CommandExecution,
#[error("Font loading failed")]
FontLoading,
#[error("Model loading failed")]
ModelLoading,
#[error("IO operation failed")]
Io,
#[error("REPL operation failed")]
Repl,
#[error("Unknown error occurred")]
Unknown,
}
#[derive(Debug)]
pub struct ZenyxError {
kind: ZenyxErrorKind,
message: Option<String>,
context: Option<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl ZenyxError {
pub fn builder(kind: ZenyxErrorKind) -> Self {
Self {
kind,
message: None,
context: None,
source: None,
}
}
pub fn kind(&self) -> &ZenyxErrorKind {
&self.kind
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
pub fn with_source<E>(mut self, source: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
self.source = Some(Box::new(source));
self
}
pub fn build(self) -> Self {
self
}
pub fn pretty_print(&self) {
let mut output = String::new();
let padding_spaces = 2;
writeln!(
output,
"{} {}",
"\nERROR:".red().bold(),
format!("{}", self.kind).bright_white().bold()
)
.unwrap();
if let Some(msg) = &self.message {
let line_padding = " ".repeat(padding_spaces);
writeln!(output, "{}>> {}\x1b[0m", line_padding, msg.bright_white()).unwrap();
}
if let Some(ctx) = &self.context {
let line_padding = " ".repeat(padding_spaces);
writeln!(output, "{}│\x1b[0m", line_padding.bright_white().bold()).unwrap();
writeln!(output, "{}╰─ Note: {}\x1b[0m", line_padding, ctx).unwrap();
}
if let Some(source) = &self.source {
let line_padding = " ".repeat(padding_spaces);
writeln!(output, "{}╰─ Caused by: {}\x1b[0m", line_padding, source).unwrap();
let mut current = source.source();
let mut depth = 1;
while let Some(err) = current {
let indent = " ".repeat(padding_spaces * depth);
writeln!(output, "{}↳ {}\x1b[0m", indent, err).unwrap();
depth += 1;
current = err.source();
}
}
print!("{}", output);
}
}
impl std::fmt::Display for ZenyxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}{}{}",
self.kind,
self.message
.as_ref()
.map_or("".to_string(), |msg| format!(" - {}", msg)),
self.context
.as_ref()
.map_or("".to_string(), |ctx| format!(" [{}]", ctx)),
self.source
.as_ref()
.map_or("".to_string(), |src| format!(" - caused by: {}", src))
)
}
}
impl std::error::Error for ZenyxError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|s| s.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl From<std::io::Error> for ZenyxError {
fn from(err: std::io::Error) -> Self {
Self::builder(ZenyxErrorKind::Io)
.with_message(err.to_string())
.with_source(err)
.build()
}
}
impl From<wgpu::CreateSurfaceError> for ZenyxError {
fn from(err: wgpu::CreateSurfaceError) -> Self {
Self::builder(ZenyxErrorKind::SurfaceCreation)
.with_message("Failed to create surface")
.with_source(err)
.build()
}
}
impl From<wgpu::RequestDeviceError> for ZenyxError {
fn from(err: wgpu::RequestDeviceError) -> Self {
Self::builder(ZenyxErrorKind::DeviceRequest)
.with_message("Failed to request device")
.with_source(err)
.build()
}
}
impl From<rustyline::error::ReadlineError> for ZenyxError {
fn from(err: rustyline::error::ReadlineError) -> Self {
Self::builder(ZenyxErrorKind::Repl)
.with_message("Readline error occurred")
.with_source(err)
.build()
}
}
pub type Result<T> = std::result::Result<T, ZenyxError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zenyx_error_builder() {
let error = ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("An IO error occurred")
.with_context("Reading file")
.build();
assert_eq!(error.kind, ZenyxErrorKind::Io);
assert_eq!(error.message.as_deref(), Some("An IO error occurred"));
assert_eq!(error.context.as_deref(), Some("Reading file"));
assert!(error.source.is_none());
}
#[test]
fn test_zenyx_error_with_source() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
let error = ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("An IO error occurred")
.with_source(io_error)
.build();
assert_eq!(error.kind, ZenyxErrorKind::Io);
assert_eq!(error.message.as_deref(), Some("An IO error occurred"));
assert!(error.source.is_some());
}
#[test]
fn test_from_io_error() {
let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
let error: ZenyxError = io_error.into();
assert_eq!(error.kind, ZenyxErrorKind::Io);
assert_eq!(error.message.as_deref(), Some("Access denied"));
assert!(error.source.is_some());
}
#[test]
fn test_from_rustyline_error() {
let readline_error = rustyline::error::ReadlineError::Interrupted;
let error: ZenyxError = readline_error.into();
assert_eq!(error.kind, ZenyxErrorKind::Repl);
assert_eq!(error.message.as_deref(), Some("Readline error occurred"));
assert!(error.source.is_some());
}
#[test]
fn test_print() {
let readline_error = rustyline::error::ReadlineError::Interrupted;
let error: ZenyxError = readline_error.into();
println!("{error}");
}
#[test]
fn test_pretty_print() {
let readline_error = rustyline::error::ReadlineError::Interrupted;
let error: ZenyxError = readline_error.into();
error.pretty_print();
}
#[test]
fn test_error_source_chain() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
let error = ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("An IO error occurred")
.with_source(io_error)
.build();
let mut source = std::error::Error::source(&error);
assert!(source.is_some());
assert_eq!(source.unwrap().to_string(), "File not found");
source = source.unwrap().source();
assert!(source.is_none());
}
}

View file

@ -1,97 +0,0 @@
use core::{panic::set_panic_hook, repl::setup, splash};
use std::{fs::OpenOptions, io::BufWriter};
use colored::Colorize;
use tokio::runtime;
use tracing::level_filters::LevelFilter;
#[allow(unused_imports)]
use tracing::{debug, error, info, warn};
use tracing_subscriber::{Registry, fmt, layer::SubscriberExt};
use winit::event_loop::EventLoop;
pub mod cli;
pub mod core;
pub mod error;
pub mod metadata;
fn init_logger() {
let stdout_layer = fmt::layer()
.with_level(true)
.compact()
.pretty()
.log_internal_errors(false)
.without_time()
.with_thread_names(true);
let file_layer = fmt::layer()
.with_level(true)
.compact()
.with_ansi(false)
.log_internal_errors(false)
.without_time()
.with_writer(|| {
let file = OpenOptions::new()
.write(true)
.append(true)
.open("zenyx.log")
.unwrap_or_else(|_| {
eprintln!("Couldn't open log file, creating a new one.");
OpenOptions::new()
.write(true)
.create(true)
.open("zenyx.log")
.expect("Failed to create log file")
});
BufWriter::new(file)
})
.with_thread_names(true);
let subscriber = Registry::default()
.with(LevelFilter::DEBUG)
.with(stdout_layer)
.with(file_layer);
tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber");
}
#[tokio::main]
async fn main() {
init_logger();
cli::parse();
let sysinfo = crate::metadata::SystemMetadata::current();
// set_panic_hook();
setup();
splash::print_splash();
if !cfg!(debug_assertions) {
info!("{}", "Debug mode disabled".bright_blue());
set_panic_hook();
} else {
println!("{}", sysinfo.verbose_summary());
}
info!("Type 'help' for a list of commands.");
let repl_thread = std::thread::spawn(|| {
let rt = match runtime::Builder::new_current_thread().enable_all().build() {
Ok(rt) => rt,
Err(e) => {
error!("A fatal error has occured: {e}");
std::process::exit(1)
}
};
rt.block_on(core::repl::input::handle_repl())
});
match EventLoop::new() {
Ok(event_loop) => {
core::render::init_renderer(event_loop);
}
Err(e) => {
error!("{e}")
}
};
if let Err(_) = repl_thread.join() {
error!("REPL thread panicked");
}
}

View file

@ -1,816 +0,0 @@
use std::collections::HashSet;
use std::fmt;
use std::str::FromStr;
use std::{env, error::Error, path::PathBuf, thread};
use native_dialog::{MessageDialog, MessageType};
use parking_lot::Once;
use raw_cpuid::CpuId;
use sysinfo::{CpuRefreshKind, RefreshKind, System};
use tracing::error;
use wgpu::DeviceType;
mod build_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}
static INIT: Once = Once::new();
pub fn set_panic_hook() {
INIT.call_once(|| {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if let Err(e) = process_panic(info) {
eprintln!("Error in panic hook: {}", e);
default_hook(info);
}
std::process::exit(1);
}));
});
}
fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box<dyn Error>> {
use std::io::Write;
use colored::Colorize;
let log_dir = PathBuf::from_str("./").expect("wtf, The current directory no longer exists?");
if !log_dir.exists() {
std::fs::create_dir_all(&log_dir)?;
}
let log_path = log_dir.join("panic.log");
let mut file = std::fs::File::create(&log_path)?;
let payload = info.payload();
let payload_str = if let Some(s) = payload.downcast_ref::<&str>() {
*s
} else if let Some(s) = payload.downcast_ref::<String>() {
s
} else {
"<non-string panic payload>"
};
writeln!(file, "Panic Occurred: {}", payload_str)?;
if let Some(location) = info.location() {
writeln!(file, "Panic Location: {}", location)?;
}
writeln!(file, "{}", capture_backtrace().sanitize_path())?;
// Add more contextual information
writeln!(file, "\n--- Additional Information ---")?;
// Rust Version
if let Ok(rust_version) = rust_version() {
writeln!(file, "Rust Version: {}", rust_version)?;
}
// Command-line Arguments
writeln!(file, "Command-line Arguments:")?;
for arg in env::args() {
writeln!(file, " {}", arg)?;
}
// Environment Variables (consider filtering sensitive ones)
writeln!(file, "\nEnvironment Variables (selected):")?;
let interesting_env_vars = ["PATH", "RUST_VERSION", "CARGO_TARGET_DIR", "HOME", "USER"];
for (key, value) in env::vars() {
if interesting_env_vars.contains(&key.as_str()) {
writeln!(file, " {}: {}", key, value)?;
}
}
// Current Working Directory
if let Ok(cwd) = env::current_dir() {
writeln!(file, "\nCurrent Working Directory: {}", cwd.display())?;
}
// Thread Information
if let Some(thread) = thread::current().name() {
writeln!(file, "\nThread Name: {}", thread)?;
} else {
writeln!(file, "\nThread ID: {:?}", thread::current().id())?;
}
let panic_msg = format!(
r#"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
We have generated a detailed 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:
https://codeberg.org/Caznix/Zenyx/issues
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
Thank you kindly!"#,
log_path.display()
);
let final_msg = format!(
r#"{}
For future reference, the error summary is as follows:
{}
More details can be found in the crash report file."#,
panic_msg, payload_str
);
println!("{}", final_msg.red().bold());
if let Err(e) = MessageDialog::new()
.set_type(MessageType::Error)
.set_title("A fatal error in Zenyx has occurred")
.set_text(&final_msg)
.show_confirm()
{
error!("Failed to show message dialog: {e}")
}
Ok(())
}
fn rust_version() -> Result<String, Box<dyn Error>> {
let version = env!("CARGO_PKG_RUST_VERSION");
Ok(version.to_string())
}
fn capture_backtrace() -> String {
let mut backtrace = String::new();
let sysinfo = SystemMetadata::current();
backtrace.push_str(&format!(
"--- System Information ---\n{}\n",
sysinfo.verbose_summary()
));
let trace = std::backtrace::Backtrace::force_capture();
let message = "\n--- Backtrace ---\n\n".to_string();
backtrace.push_str(&message);
backtrace.push_str(&format!("{trace:#}"));
backtrace
}
trait Sanitize {
fn sanitize_path(&self) -> String;
}
impl Sanitize for str {
fn sanitize_path(&self) -> String {
let prefixes = ["/home/", "/Users/", "\\Users\\", "/opt/home/"];
let mut result = String::from(self);
for prefix in prefixes {
if let Some(start_index) = result.find(prefix) {
let start_of_user = start_index + prefix.len();
let mut end_of_user = result[start_of_user..]
.find(|c| c == '/' || c == '\\')
.map(|i| start_of_user + i)
.unwrap_or(result.len());
if end_of_user == start_of_user && start_of_user < result.len() {
end_of_user = result.len();
}
result.replace_range(start_of_user..end_of_user, "<USER>");
break;
}
}
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Memory {
bytes: u64,
}
impl Memory {
pub const fn from_bytes(bytes: u64) -> Self {
Self { bytes }
}
pub const fn from_kb(kb: u64) -> Self {
Self { bytes: kb * 1024 }
}
pub const fn from_mb(mb: u64) -> Self {
Self {
bytes: mb * 1024 * 1024,
}
}
pub const fn from_gb(gb: u64) -> Self {
Self {
bytes: gb * 1024 * 1024 * 1024,
}
}
pub const fn as_bytes(&self) -> u64 {
self.bytes
}
pub const fn as_kb(&self) -> u64 {
self.bytes / 1024
}
pub const fn as_mb(&self) -> u64 {
self.bytes / (1024 * 1024)
}
pub const fn as_gb(&self) -> u64 {
self.bytes / (1024 * 1024 * 1024)
}
pub fn format_human(&self) -> String {
const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
let mut size = self.bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
format!("{:.2} {}", size, UNITS[unit_index])
}
pub fn verbose_info(&self) -> String {
format!(
"{} ({} bytes, {} KB, {} MB, {} GB)",
self.format_human(),
self.as_bytes(),
self.as_kb(),
self.as_mb(),
self.as_gb()
)
}
}
impl fmt::Display for Memory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.format_human())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CPUBrand {
Intel,
AMD,
SnapDragon,
Apple,
Other(String),
}
impl From<&str> for CPUBrand {
fn from(s: &str) -> Self {
let sl = s.to_lowercase();
if sl.contains("intel") {
Self::Intel
} else if sl.contains("amd") {
Self::AMD
} else if sl.contains("snapdragon") {
Self::SnapDragon
} else if sl.contains("apple") {
Self::Apple
} else {
Self::Other(s.to_string())
}
}
}
impl fmt::Display for CPUBrand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Intel => write!(f, "Intel"),
Self::AMD => write!(f, "AMD"),
Self::SnapDragon => write!(f, "SnapDragon"),
Self::Apple => write!(f, "Apple"),
Self::Other(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CPUArch {
X86,
X86_64,
AArch64,
Other(String),
}
impl FromStr for CPUArch {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"x86" => Self::X86,
"x86_64" => Self::X86_64,
"arm" => Self::AArch64,
"aarch64" => Self::AArch64,
_ => Self::Other(s.to_string()),
})
}
}
impl fmt::Display for CPUArch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::X86 => write!(f, "x86"),
Self::X86_64 => write!(f, "x86_64"),
Self::AArch64 => write!(f, "AArch64"),
Self::Other(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClockSpeed(pub u32);
impl ClockSpeed {
pub fn as_mhz(&self) -> u32 {
self.0
}
pub fn as_ghz(&self) -> f32 {
self.0 as f32 / 1000.0
}
}
impl fmt::Display for ClockSpeed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.2} GHz ({} MHz)", self.as_ghz(), self.as_mhz())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CPU {
pub brand: CPUBrand,
pub arch: CPUArch,
pub name: String,
pub vendor_id: String,
pub physical_cores: Option<u8>,
pub logical_cores: Option<u8>,
pub max_clock_speed: ClockSpeed,
pub current_clock_speed: ClockSpeed,
pub l1_cache: Option<Memory>,
pub l2_cache: Option<Memory>,
pub l3_cache: Option<Memory>,
}
impl CPU {
pub fn current() -> Self {
let mut sys = System::new_with_specifics(
RefreshKind::default().with_cpu(CpuRefreshKind::everything()),
);
sys.refresh_cpu_all();
let cpu_opt = sys.cpus().first();
let brand = cpu_opt
.map(|cpu| cpu.brand().into())
.unwrap_or(CPUBrand::Other("unknown".into()));
let name = cpu_opt
.map(|cpu| cpu.name().to_string())
.unwrap_or_else(|| "unknown".into());
let vendor_id = cpu_opt
.map(|cpu| cpu.vendor_id().to_string())
.unwrap_or_else(|| "unknown".into());
let max_clock_speed = cpu_opt
.map(|cpu| ClockSpeed(cpu.frequency() as u32))
.unwrap_or(ClockSpeed(0));
let current_clock_speed = max_clock_speed;
let logical_cores = cpu_opt.map(|_| sys.cpus().len() as u8);
let physical_cores = System::physical_core_count().map(|pc| pc as u8);
let mut l1_cache = None;
let mut l2_cache = None;
let mut l3_cache = None;
#[cfg(any(
all(target_arch = "x86", not(target_env = "sgx"), target_feature = "sse"),
all(target_arch = "x86_64", not(target_env = "sgx"))
))]
{
let cpuid = CpuId::new();
if let Some(iter) = cpuid.get_cache_parameters() {
for cache in iter {
match cache.level() {
1 => {
let size = cache.physical_line_partitions()
* cache.coherency_line_size()
* cache.associativity();
if size > 0 {
l1_cache = Some(Memory::from_bytes(size.try_into().unwrap()));
}
}
2 => {
let size = cache.physical_line_partitions()
* cache.coherency_line_size()
* cache.associativity();
if size > 0 {
l2_cache = Some(Memory::from_bytes(size.try_into().unwrap()));
}
}
3 => {
let size = (cache.physical_line_partitions() as u64)
* (cache.coherency_line_size() as u64)
* (cache.associativity() as u64);
if size > 0 {
l3_cache = Some(Memory::from_bytes(size));
}
}
_ => {}
}
}
}
}
Self {
brand,
arch: env::consts::ARCH
.parse()
.unwrap_or(CPUArch::Other("unknown".into())),
name,
vendor_id,
physical_cores,
logical_cores,
max_clock_speed,
current_clock_speed,
l1_cache,
l2_cache,
l3_cache,
}
}
pub fn is_intel(&self) -> bool {
matches!(self.brand, CPUBrand::Intel)
}
pub fn is_amd(&self) -> bool {
matches!(self.brand, CPUBrand::AMD)
}
pub fn is_arm(&self) -> bool {
matches!(self.brand, CPUBrand::Intel)
}
pub fn is_high_clock(&self) -> bool {
self.max_clock_speed.0 > 3000
}
pub fn verbose_info(&self) -> String {
format!(
"CPU Information:\n\
- Brand: {}\n\
- Architecture: {}\n\
- Name: {}\n\
- Vendor ID: {}\n\
- Cores: {} physical, {} logical\n\
- Clock Speed: {} (max), {} (current)\n\
- Cache: L1 {}, L2 {}, L3 {}",
self.brand,
self.arch,
self.name,
self.vendor_id,
self.physical_cores
.map(|c| c.to_string())
.unwrap_or_else(|| "unknown".into()),
self.logical_cores
.map(|c| c.to_string())
.unwrap_or_else(|| "unknown".into()),
self.max_clock_speed,
self.current_clock_speed,
self.l1_cache
.map(|c| c.format_human())
.unwrap_or_else(|| "unknown".into()),
self.l2_cache
.map(|c| c.format_human())
.unwrap_or_else(|| "unknown".into()),
self.l3_cache
.map(|c| c.format_human())
.unwrap_or_else(|| "unknown".into()),
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum GPUBrand {
Nvidia,
AMD,
Intel,
Other(String),
}
impl From<&str> for GPUBrand {
fn from(s: &str) -> Self {
let sl = s.to_lowercase();
if sl.contains("nvidia") || sl.contains("geforce") {
Self::Nvidia
} else if sl.contains("amd") || sl.contains("radeon") {
Self::AMD
} else if sl.contains("intel") {
Self::Intel
} else {
Self::Other(s.to_string())
}
}
}
impl fmt::Display for GPUBrand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nvidia => write!(f, "NVIDIA"),
Self::AMD => write!(f, "AMD"),
Self::Intel => write!(f, "Intel"),
Self::Other(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GPU {
pub brand: GPUBrand,
pub name: String,
pub device_type: DeviceType,
pub vram: Memory,
pub driver_version: Option<String>,
}
impl GPU {
pub fn current() -> Vec<Self> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
instance
.enumerate_adapters(wgpu::Backends::all())
.iter()
.map(|adapter| {
let info = adapter.get_info();
GPU {
brand: info.name.as_str().into(),
name: info.name.to_string(),
device_type: info.device_type,
vram: Memory::from_bytes(0),
driver_version: Some(info.driver.to_string()),
}
})
.collect()
}
pub fn is_integrated(&self) -> bool {
matches!(self.brand, GPUBrand::Intel)
}
pub fn is_dedicated(&self) -> bool {
!self.is_integrated()
}
pub fn is_mobile(&self) -> bool {
let lower_name = self.name.to_lowercase();
lower_name.contains("adreno")
|| lower_name.contains("mali")
|| lower_name.contains("apple")
|| lower_name.contains("mobile")
|| lower_name.contains("snapdragon")
}
pub fn verbose_info(&self) -> String {
format!(
"GPU Information:\n\
- Brand: {}\n\
- Name: {}\n\
- VRAM: {}\n\
- Driver: {}",
self.brand,
self.name,
self.vram.format_human(),
self.driver_version.as_deref().unwrap_or("Unknown")
)
}
fn unique_id(&self) -> String {
format!("{}-{:?}", self.name, self.device_type)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SystemMemory {
pub total: Memory,
pub used: Memory,
pub free: Memory,
pub available: Memory,
pub swap_total: Memory,
pub swap_used: Memory,
pub swap_free: Memory,
}
impl SystemMemory {
pub fn current() -> Self {
let mut system = System::new();
system.refresh_memory();
Self {
total: Memory::from_bytes(system.total_memory()),
used: Memory::from_bytes(system.used_memory()),
free: Memory::from_bytes(system.free_memory()),
available: Memory::from_bytes(system.available_memory()),
swap_total: Memory::from_bytes(system.total_swap()),
swap_used: Memory::from_bytes(system.used_swap()),
swap_free: Memory::from_bytes(system.free_swap()),
}
}
pub fn verbose_info(&self) -> String {
format!(
"Memory Information:\n\
- RAM: {} total, {} used, {} free, {} available\n\
- Swap: {} total, {} used, {} free",
self.total.format_human(),
self.used.format_human(),
self.free.format_human(),
self.available.format_human(),
self.swap_total.format_human(),
self.swap_used.format_human(),
self.swap_free.format_human()
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EngineInfo {
pub timestamp: String,
pub pkg_version: String,
pub pkg_name: String,
pub target_arch: String,
pub target_os: String,
pub target_env: String,
pub rustc_version: String,
pub wgpu_version: String,
pub winit_version: String,
pub commit_hash: String,
}
impl EngineInfo {
pub fn current() -> Self {
Self {
timestamp: build_info::BUILT_TIME_UTC.to_string(),
pkg_version: build_info::PKG_VERSION.to_string(),
pkg_name: build_info::PKG_NAME.to_string(),
target_arch: build_info::CFG_TARGET_ARCH.to_string(),
target_os: build_info::CFG_OS.to_string(),
target_env: build_info::CFG_ENV.to_string(),
rustc_version: build_info::RUSTC_VERSION.to_string(),
wgpu_version: build_info::WGPU_VERSION.to_string(),
winit_version: build_info::WGPU_VERSION.to_string(),
commit_hash: build_info::GIT_COMMIT_HASH
.unwrap_or(&format!("UNKNOWN-{:?}", std::time::SystemTime::now()))
.to_string(),
}
}
pub fn verbose_info(&self) -> String {
format!(
"Build Information:\n\
- Timestamp: {}\n\
- Package: {} v{}\n\
- Target: {} {} {}\n\
- Rustc version: {}\n\
- Wgpu version: {}\n\
- Winit version: {}\n\
- commit_hash: {}\n\
",
self.timestamp,
self.pkg_name,
self.pkg_version,
self.target_arch,
self.target_os,
self.target_env,
self.rustc_version,
self.wgpu_version,
self.winit_version,
self.commit_hash
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SystemMetadata {
pub cpu: CPU,
pub memory: SystemMemory,
pub gpus: Vec<GPU>,
pub compile_info: EngineInfo,
pub os_info: OSInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OSInfo {
pub name: String,
pub version: Option<String>,
pub kernel_version: Option<String>,
}
impl OSInfo {
pub fn current() -> Self {
let mut system = System::new();
system.refresh_all();
Self {
name: System::name().unwrap_or_else(|| build_info::TARGET.to_string()),
version: System::os_version(),
kernel_version: System::kernel_version(),
}
}
pub fn verbose_info(&self) -> String {
format!(
"Operating System Information:\n\
- Name: {}\n\
- Version: {}\n\
- Kernel Version: {}",
self.name,
self.version.as_deref().unwrap_or("Unknown"),
self.kernel_version.as_deref().unwrap_or("Unknown")
)
}
}
impl SystemMetadata {
pub fn current() -> Self {
Self {
cpu: CPU::current(),
memory: SystemMemory::current(),
gpus: GPU::current(),
compile_info: EngineInfo::current(),
os_info: OSInfo::current(),
}
}
pub fn main_gpu(&self) -> Option<&GPU> {
self.gpus
.iter()
.find(|g| g.is_dedicated())
.or_else(|| self.gpus.iter().find(|g| g.is_integrated()))
.or_else(|| self.gpus.first())
}
pub fn verbose_summary(&self) -> String {
let main_gpu = self.main_gpu();
let main_gpu_info = main_gpu
.map(|gpu| format!("Main GPU:\n{}", gpu.verbose_info()))
.unwrap_or_else(|| "No main GPU detected".to_string());
let mut seen_gpu_ids = HashSet::new();
let mut other_gpus_info = Vec::new();
if let Some(gpu) = main_gpu {
seen_gpu_ids.insert(gpu.unique_id());
}
for gpu in &self.gpus {
let gpu_id = gpu.unique_id();
if !seen_gpu_ids.contains(&gpu_id) {
other_gpus_info.push(format!("[{:?}] {}", gpu.device_type, gpu.verbose_info()));
seen_gpu_ids.insert(gpu_id);
}
}
let other_gpu_list = if other_gpus_info.is_empty() {
String::new()
} else {
format!("Other GPUs:\n{}", other_gpus_info.join("\n\n"))
};
format!(
"System Information:\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}",
self.os_info.verbose_info(),
self.cpu.verbose_info(),
main_gpu_info,
other_gpu_list,
self.memory.verbose_info(),
self.compile_info.verbose_info()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_conversions() {
let mem = Memory::from_gb(8);
assert_eq!(mem.as_bytes(), 8 * 1024 * 1024 * 1024);
assert_eq!(mem.as_mb(), 8 * 1024);
assert_eq!(mem.as_kb(), 8 * 1024 * 1024);
}
#[test]
fn test_system_metadata() {
let metadata = SystemMetadata::current();
assert!(!metadata.cpu.name.is_empty());
assert!(metadata.memory.total.as_bytes() > 0);
assert!(!metadata.compile_info.pkg_version.is_empty());
assert!(!metadata.os_info.name.is_empty());
}
}

View file

@ -1 +0,0 @@
exec ./test.zensh

View file

@ -1 +0,0 @@
exit "test"

View file

@ -4,4 +4,4 @@ format_code_in_doc_comments = true
trailing_comma = "Vertical"
group_imports = "StdExternalCrate"
reorder_impl_items = true
unstable_features = true
unstable_features = true

6
src/main.rs Normal file
View file

@ -0,0 +1,6 @@
fn main() {
println!("Happy Halloween!")
}

View file

@ -1,15 +0,0 @@
[package]
name = "zen_core"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.94"
thiserror = "2.0.8"
parking_lot.workspace = true
[profile.dev]
debug-assertions = true
[profile.release]
debug-assertions = false

View file

@ -1,7 +0,0 @@
use thiserror::Error;
#[derive(Debug, Error)]
enum ZError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}

View file

@ -1,2 +0,0 @@
# Blender 4.2.3 LTS MTL File: 'None'
# www.blender.org

View file

@ -1,4 +0,0 @@
[rendering]
force_vsync = true
[settings]