2 Commits

8 changed files with 258 additions and 21 deletions

View File

@@ -1,7 +1,7 @@
# RustyDoom
A Rust version of the original DOOM from the offically released source code found at [https://github.com/id-software/doom](https://github.com/id-software/doom)
A Rust version of the original DOOM base on the offically released source code found at [https://github.com/id-software/doom](https://github.com/id-software/doom)
This is more of a learning exercise than something good!
This is more of a learning exercise than something good! For example, the original `z_zone.c` and `z_zone.h` will be more "Rust-like" compared to the original version.
This repository will only contain the SHAREWARE verison of the original DOOM1, but will be able to run both IWAD and PWADS.

View File

@@ -10,7 +10,9 @@ use crate::m_argv::M_GetOptionalArgumentValueByArgument;
use crate::m_argv::PARM_NOT_FOUND;
use crate::m_misc::M_LoadDefaults;
use crate::v_video::V_Init;
use crate::w_wad::{W_CheckNumForName, W_InitMultipleFiles};
use crate::z_zone::Z_Init;
use crate::doomdef::{
VERSION,
@@ -400,19 +402,16 @@ pub fn D_DoomMain() {
}
}
// TODO: Implement subsytem inits
/*
println!("V_Init: allocate screens.");
V_Init();
*/
println!("M_LoadDefaults: Load system defaults.");
M_LoadDefaults();
/*
println!("Z_init: Init zone memory allocation daemon.");
Z_Init();
*/
println!("W_Init: Init WADfiles");
W_InitMultipleFiles(DOOMGLOBALS::with_ref(|g| g.wadfiles.clone())); // we are loading so how cares about a borrow

View File

@@ -1,7 +1,35 @@
use std::cell::RefCell;
/// DOOM version
pub const VERSION:i32 = 110;
pub const D_DEVSTR: &str = "Development mode ON.";
/// BASE_WIDTH
/// For resize of screen, at start of game.
/// It will not work dynamically, see visplanes. TODO: Investigate what this means.
pub const BASE_WIDTH: i32 = 320;
/// Screen scale multiplier?
/// Original source comment:
/// It is educational but futile to change this
/// scaling e.g. to 2. Drawing of status bar,
/// menues etc. is tied to the scale implied
/// by the graphics.
pub const SCREEN_MUL: i32 = 1;
/// Inverse of the aspect ratio
pub const INV_ASPECT_RATIO: f32 = 0.625; // 0.75, ideally according to the original source
/// SCREENWIDTH
/// = SCREEN_MUL*BASE_WIDTH //320
pub const SCREENWIDTH: i32 = 320;
/// SCREENHEIGHT
/// (int)(SCREEN_MUL*BASE_WIDTH*INV_ASPECT_RATIO) //200
pub const SCREENHEIGHT:i32 = 200;
/// The maximum number of players, multiplayer/networking.
pub const MAXPLAYERS: i32 = 4;
/// The number of state updates (ticks) to be done per second
pub const TICRATE: i32 = 35;

10
src/doomtype/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
const MAXCHAR: i8 = i8::MAX;
const MAXSHORT: i16 = i16::MAX;
const MAXINT: i32 = i32::MAX;
const MAXLONG: i32 = i32::MAX;
// Minimum values
const MINCHAR: i8 = i8::MIN;
const MINSHORT: i16 = i16::MIN;
const MININT: i32 = i32::MIN;
const MINLONG: i32 = i32::MIN;

View File

@@ -1,20 +1,10 @@
use std::ptr::addr_of_mut;
use crate::d_strings;
use crate::doomtype;
use crate::hu_stuff::get_chat_macro_ptr;
use crate::m_argv::{M_CheckParm, M_GetOptionalArgumentValueByArgument, PARM_NOT_FOUND};
const MAXCHAR: i8 = i8::MAX;
const MAXSHORT: i16 = i16::MAX;
const MAXINT: i32 = i32::MAX;
const MAXLONG: i32 = i32::MAX;
// Minimum values
const MINCHAR: i8 = i8::MIN;
const MINSHORT: i16 = i16::MIN;
const MININT: i32 = i32::MIN;
const MINLONG: i32 = i32::MIN;
// static mut variable stuff
@@ -280,7 +270,6 @@ pub fn M_WriteFile(name: &str, source: std::fs::File, length: i64) {
}
#[allow(non_snake_case)]
pub fn M_ReadFile(name: &str, buffer: Vec<u8>) {
}

View File

@@ -1,10 +1,13 @@
mod d_main;
mod d_strings;
mod doomdef;
mod doomtype;
mod hu_stuff;
mod m_argv;
mod m_misc;
mod v_video;
mod w_wad;
mod z_zone;
mod math;

43
src/v_video/mod.rs Normal file
View File

@@ -0,0 +1,43 @@
use crate::doomdef::{SCREENHEIGHT, SCREENWIDTH};
use crate::doomtype;
pub const CENTERY: i32 = SCREENHEIGHT / 2;
// static var stuff
#[allow(non_upper_case_globals)]
static mut screens: [[u8; (SCREENWIDTH*SCREENHEIGHT) as usize]; 5] = [[0u8; (SCREENWIDTH*SCREENHEIGHT) as usize]; 5];
#[allow(non_snake_case)]
pub fn V_Init() {
// Do nothing :)
// the memory is already allocated
}
#[allow(non_snake_case)]
pub fn V_CopyRect(srcx: i32, srcy: i32, srcscrn: i32, width: i32, height: i32, destx: i32, desty: i32, destscrn: i32) {
}
/*
TODO: Implement "r_data"
#[allow(non_snake_case)]
pub fn V_DrawPatch(x: i32, y: i32, scrn: i32, patch: Vec<patch_t>) {
}
#[allow(non_snake_case)]
pub fn V_DrawPatchDirect(x: i32, y: i32, scrn: i32, patch: Vec<patch_t>) {
}
*/
#[allow(non_snake_case)]
pub fn V_GetBlock(x: i32, y: i32, scrn: i32, width: i32, height: i32, dest: Vec<u8>) {
}
#[allow(non_snake_case)]
pub fn V_MarkRect(x: i32, y: i32, width: i32, height: i32) {
}

165
src/z_zone/mod.rs Normal file
View File

@@ -0,0 +1,165 @@
/// Memory allocator stuff
/// Compared to the original source, this will be some what "lean n' mean"
use std::cell::RefCell;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
/// The PurgeTags (PUs)
/// Tags < 100 are not overwritten untill freed
pub enum PurgeTag {
/// Static the entire execution time
Static = 1,
/// Static while playing
Sound = 2,
/// static while playing
Music = 3,
/// Original source comment: anything else dave wants static
Dave = 4,
/// static until level exit
Level = 50,
/// static for special thinkers in a level
LevSpec = 51,
// Tags >= 100 are "purgable" whenever needed
PurgeLevel = 100,
Cache = 101,
}
/// Provides metadata on
struct AllocMeta {
/// The size of the allocation
size: usize,
/// The PU tag associated with the allocation
tag: i32,
/// A pointer to the caller's tracking pointer, cleared automatically upon freeing
user: *mut *mut u8,
}
thread_local! {
// We promise not to be naughty and access this from another thread :)
/// Mapping from pointers (as usize) to the correspond AllocationMeta
static REGISTRY: RefCell<HashMap<usize, AllocMeta>> = RefCell::new(HashMap::new());
}
// Helper method that we can use to access the registry
fn with_registry<F, R>(f: F) -> R
where
F: FnOnce(&mut HashMap<usize, AllocMeta>) -> R
{
REGISTRY.with(|registry| f(&mut *registry.borrow_mut()))
}
#[allow(non_snake_case)]
/// Initalized the Zone memory allocator sub-system
pub fn Z_Init() {
// do nothing, we get lazy init of the registry.
}
#[allow(non_snake_case)]
/// Allocate a contiguous chunk of zeroed memory from the zone allocator
/// # Safety
/// This function returns a raw pointer that bypasses Rust's automatic memory
/// tracking. The returned memory must be manually reclaimed using `Z_Free`.
pub unsafe fn Z_Malloc(size: usize, tag: i32, user: *mut *mut u8) -> *mut u8 {
let mut buffer = vec![0u8; size];
let ptr = buffer.as_mut_ptr();
// ensures Rust does not drop the buffer at the end of this block
std::mem::forget(buffer);
// Update the engine's tracking pointer if one was provided
if !user.is_null() {
unsafe {
*user = ptr;
}
}
// Save allocation details using the pointer address as the key
with_registry(|map| {
map.insert(ptr as usize, AllocMeta { size, tag, user });
});
ptr
}
#[allow(non_snake_case)]
/// Reclaims and frees a allocation from Z_Malloc
/// # Safety
/// The provided pointer must point to a valid allocation tracked by the registry.
/// Passing an invalid or already freed pointer can result in undefined behavior
pub unsafe fn Z_Free(ptr: *mut u8) {
if ptr.is_null() {
return;
}
let removed = with_registry(|map| map.remove(&(ptr as usize)));
if let Some(meta) = removed {
// Clear the caller's tracking pointer to prevent use-after-free
if !meta.user.is_null() {
unsafe {
*meta.user = std::ptr::null_mut();
}
}
// re-build the vector so Rust can drop it
unsafe {
let _dropped_vec = Vec::from_raw_parts(ptr, meta.size, meta.size);
}
}
}
#[allow(non_snake_case)]
/// Frees all memory allocations between the specific lifetime PU tags.
/// # Safety
/// All pointers tracked within this tag boundary are invalidated and deallocate
pub unsafe fn Z_FreeTags(low_tag: i32, high_tag: i32) {
// get all of the allocations to free
let tags_to_free: Vec<usize> = with_registry(|map| {
map.iter()
.filter(|(_, meta)| meta.tag >= low_tag && meta.tag <= high_tag)
.map(|(ptr_addr,_)| *ptr_addr)
.collect()
});
// remove each pointer returned from the map and drop the memory
for ptr_addr in tags_to_free {
let removed_ptr = with_registry(|map| map.remove(&ptr_addr));
if let Some(meta) = removed_ptr {
let p = ptr_addr as *mut u8;
unsafe {
// clear caller tracking pointer to preven use after free
if !meta.user.is_null() {
*meta.user = std::ptr::null_mut();
}
let _dropped_vec = Vec::from_raw_parts(p, meta.size, meta.size);
}
}
}
}
// Internal ChangeTag function
#[allow(non_snake_case)]
fn Z_ChangeTagInternal(ptr: *mut u8, tag: i32) {
if ptr.is_null() {
return;
}
with_registry(|map| {
if let Some(meta) = map.get_mut(&(ptr as usize)) {
meta.tag = tag;
}
})
}
/// Modifies the lifetime tag of an active allocation.
///
/// # Safety
/// The provided pointer must point to a valid allocation tracked by the registry
#[allow(non_snake_case)]
pub unsafe fn Z_ChangeTag(ptr: *mut u8, tag: i32) {
// removes the original helper macro from the original C, we don't need withcraft here
Z_ChangeTagInternal(ptr, tag);
}