Skip to content

Commit 82d62c1

Browse files
committedJan 5, 2020
54-58 (bugged) not pulling in trees
1 parent f9a8dda commit 82d62c1

7 files changed

+594
-499
lines changed
 

‎Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ path = "src/win32_handmade.rs"
2727

2828
[profile.dev]
2929
# optimize dev builds needed to match hh performance , uncomment for visual studio debugger to work better
30-
opt-level = 3
31-
overflow-checks = false
30+
# opt-level = 3
31+
overflow-checks = false

‎README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ https://handmadehero.org/watch
33

44
HanmadeHero is a game made from scratch as much as reasonably possible*. I'm trying to port it to rust to make learning more interactive, engaging, and to have a 1:1 program comparison of rust vs c++.
55

6+
## experience thus far:
7+
Porting HmH in safe rust has been an uphill battle with the borrow checker because mutable aliasing is allowed in C++, and casey uses a lot of it.
8+
I advise for those who want to learn rust and lessons from HmH to use SDL and start off writing things the rust way.
9+
610
## For future gamedev reference:
711

812
* e18: 1:13:00 debunks why you shouldn't add previous frame time to compensate for missed frame
13+
* ep35: 1:20:14 why not consider teleporting as a means to substitute 3D positioning for moving up floors
914
* ep38 1:29:00 linear alpha blend, one of the most important math equations in game
1015
* ep42-44: why linear algebra is very useful and makes programming math more simpler
1116
## todo:
@@ -24,9 +29,10 @@ HanmadeHero is a game made from scratch as much as reasonably possible*. I'm try
2429
* assign calls to cstring!("") to a variable first, then .as_ptr(), otherwise they return empty: https://stackoverflow.com/questions/52174925/cstringnew-unwrap-as-ptr-gives-empty-const-c-char
2530
* try to port to linux after opengl(ep 200+)
2631
* b"string" is better than "string".as_bytes() because it retains information about the length
32+
* When an unsigned int and an int are added together, the int is first converted to unsigned int before the addition takes place (and the result is also an unsigned int).
2733

2834
# bugs:
29-
* replay is not playing back from recorded game state
35+
* replay is not playing back from recorded game state (seems to be fixed now)
3036
* fullscreen does not work, see line 1047 in platform layer
3137

3238
# disclaimer!

‎src/handmade.rs

+260-181
Large diffs are not rendered by default.

‎src/handmade_math.rs

+16
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ pub fn LengthSq(a: v2) -> f32 {
9191
let result = Inner(a, a);
9292
return result;
9393
}
94+
95+
pub fn GetMinCorner(Rect: rectangle2) -> v2 {
96+
let result = Rect.Min;
97+
return (result);
98+
}
99+
100+
pub fn GetMaxCorner(Rect: rectangle2) -> v2 {
101+
let result = Rect.Max;
102+
return (result);
103+
}
104+
105+
pub fn GetCenter(Rect: rectangle2) -> v2 {
106+
let result = 0.5 * (Rect.Min + Rect.Max);
107+
return (result);
108+
}
109+
94110
#[derive(Default, Copy, Clone)]
95111
pub struct rectangle2 {
96112
Min: v2,

‎src/handmade_tile.rs

-314
This file was deleted.

‎src/handmade_world.rs

+309
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
#![allow(allow_bad_style)]
2+
use crate::handmade_math::v2;
3+
use crate::memory_arena;
4+
use crate::PushStruct;
5+
use core::ptr::null_mut;
6+
use std::convert::TryInto;
7+
#[derive(Default)]
8+
pub struct world_difference {
9+
pub dXY: v2,
10+
pub dZ: f32,
11+
}
12+
#[derive(Default, Copy, Clone)]
13+
pub struct world_position {
14+
// TODO(casey): Puzzler! How can we get rid of abstile* here,
15+
// and still allow references to entities to be able to figure
16+
// out _where they are_ (or rather, which world_chunk they are
17+
// in?)
18+
pub ChunkX: i32,
19+
pub ChunkY: i32,
20+
pub ChunkZ: i32,
21+
22+
// NOTE(casey): These are the offsets from the chunk center
23+
pub Offset_: v2,
24+
}
25+
26+
// TODO(casey): Could make this just tile_chunk and then allow multiple tile chunks per X/Y/Z
27+
//NO WAY TO CLONE IF NEXT HOLDED A MUTABLE REFERENCE. YOU CANNOT CLONE MUTABLE REFERENCES
28+
#[derive(Clone, Copy)]
29+
pub struct world_entity_block {
30+
pub EntityCount: u32,
31+
pub LowEntityIndex: [u32; 16],
32+
pub Next: Option<*mut world_entity_block>,
33+
}
34+
35+
pub struct world_chunk {
36+
pub ChunkX: i32,
37+
pub ChunkY: i32,
38+
pub ChunkZ: i32,
39+
40+
pub FirstBlock: Option<world_entity_block>,
41+
pub NextInHash: Option<*mut world_chunk>,
42+
}
43+
44+
pub struct world {
45+
pub TileSideInMeters: f32,
46+
pub ChunkSideInMeters: f32,
47+
48+
pub FirstFree: Option<*mut world_entity_block>,
49+
50+
pub ChunkHash: [world_chunk; 4096],
51+
}
52+
53+
static TILE_CHUNK_SAFE_MARGIN: i32 = (std::i32::MAX / 64);
54+
static TILE_CHUNK_UNINITIALIZED: i32 = std::i32::MAX;
55+
56+
static TILES_PER_CHUNK: u32 = 16;
57+
58+
pub fn IsCanonical(World: &world, TileRel: f32) -> bool {
59+
// TODO(casey): Fix floating point math so this can be exact?
60+
let result =
61+
(TileRel >= -0.5 * World.ChunkSideInMeters) && (TileRel <= 0.5 * World.ChunkSideInMeters);
62+
63+
return result;
64+
}
65+
66+
pub fn IsCanonical_v2(World: &world, Offset: v2) -> bool {
67+
let result = IsCanonical(World, Offset.X) && IsCanonical(World, Offset.Y);
68+
69+
return result;
70+
}
71+
72+
pub fn AreInSameChunk(World: &world, A: &world_position, B: &world_position) -> bool {
73+
let result = (A.ChunkX == B.ChunkX) && (A.ChunkY == B.ChunkY) && (A.ChunkZ == B.ChunkZ);
74+
75+
return result;
76+
}
77+
78+
//ORIGINAL FUNCTION PASSED IN ENTIRE WORLD STRUCT INSTEAD OF CHUNKHASH
79+
pub fn GetWorldChunk(
80+
ChunkHash: &mut [world_chunk],
81+
ChunkX: i32,
82+
ChunkY: i32,
83+
ChunkZ: i32,
84+
Arena: Option<*mut memory_arena>,
85+
//CHANGING THIS TO world_chunk<'b> FIXES THE PROBLEM. IF YOU DONT USE MULTIPLE LIFETIMES, THIS FUNCTION WILL TAKE AH OLD OF THE &'A MUT BORROW FOR THE ENTIRE EXISTANCE OF SCOPE IN OTHER FUNCTIONS
86+
) -> *mut world_chunk {
87+
let HashValue = 19 * ChunkX + 7 * ChunkY + 3 * ChunkZ;
88+
let HashSlot = HashValue & (ChunkHash.len() - 1) as i32;
89+
90+
let len = ChunkHash.len().try_into().unwrap();
91+
let mut Chunk = &mut ChunkHash[HashSlot as usize];
92+
let valid_arena = Arena.is_some();
93+
94+
unsafe {
95+
loop {
96+
if (ChunkX == Chunk.ChunkX) && (ChunkY == Chunk.ChunkY) && (ChunkZ == Chunk.ChunkZ) {
97+
break;
98+
}
99+
if valid_arena
100+
&& (Chunk.ChunkX != TILE_CHUNK_UNINITIALIZED)
101+
&& (!Chunk.NextInHash.is_some())
102+
{
103+
//handmade_world.rs(78, 18): lifetime `'a` defined here
104+
//mutable borrow starts here in previous iteration of loop
105+
//requires that `Arena` is borrowed for `'a`
106+
//no way but to take arena as a *mut :
107+
// see https://users.rust-lang.org/t/mutable-borrow-starts-here-in-previous-iteration-of-loop/26145/4?u=jest
108+
unsafe {
109+
Chunk.NextInHash = Some(PushStruct::<world_chunk>(&mut *Arena.unwrap()));
110+
}
111+
Chunk = &mut *(Chunk.NextInHash.unwrap());
112+
Chunk.ChunkX = TILE_CHUNK_UNINITIALIZED;
113+
}
114+
if valid_arena && (Chunk.ChunkX == TILE_CHUNK_UNINITIALIZED) {
115+
Chunk.ChunkX = ChunkX;
116+
Chunk.ChunkY = ChunkY;
117+
Chunk.ChunkZ = ChunkZ;
118+
119+
Chunk.NextInHash = None;
120+
break;
121+
}
122+
123+
if HashSlot < len {
124+
break;
125+
}
126+
Chunk = &mut *Chunk.NextInHash.unwrap();
127+
}
128+
}
129+
return Chunk as *mut world_chunk;
130+
}
131+
132+
pub fn InitializeWorld(World: &mut world, TileSideInMeters: f32) {
133+
World.TileSideInMeters = TileSideInMeters;
134+
World.ChunkSideInMeters = TILES_PER_CHUNK as f32 * TileSideInMeters;
135+
World.FirstFree = None;
136+
137+
for ChunkIndex in 0..World.ChunkHash.len() {
138+
World.ChunkHash[ChunkIndex].ChunkX = TILE_CHUNK_UNINITIALIZED;
139+
World.ChunkHash[ChunkIndex]
140+
.FirstBlock
141+
.as_mut()
142+
.unwrap()
143+
.EntityCount = 0;
144+
}
145+
}
146+
147+
pub fn RecanonicalizeCoord(World: &world, Tile: &mut i32, TileRel: &mut f32) {
148+
// TODO(casey): Need to do something that doesn't use the divide/multiply method
149+
// for recanonicalizing because this can end up rounding back on to the tile
150+
// you just came from.
151+
152+
// NOTE(casey): Wrapping IS NOT ALLOWED, so all coordinates are assumed to be
153+
// within the safe margin!
154+
// TODO(casey): Assert that we are nowhere near the edges of the world.
155+
let Offset = (*TileRel / World.ChunkSideInMeters).round() as i32;
156+
*Tile += Offset;
157+
*TileRel -= Offset as f32 * World.ChunkSideInMeters;
158+
}
159+
160+
pub fn MapIntoChunkSpace(World: &world, BasePos: world_position, Offset: v2) -> world_position {
161+
let mut result = BasePos;
162+
163+
result.Offset_ += Offset;
164+
RecanonicalizeCoord(World, &mut result.ChunkX, &mut result.Offset_.X);
165+
RecanonicalizeCoord(World, &mut result.ChunkY, &mut result.Offset_.Y);
166+
167+
return result;
168+
}
169+
170+
pub fn ChunkPositionFromTilePosition(
171+
World: &world,
172+
AbsTileX: i32,
173+
AbsTileY: i32,
174+
AbsTileZ: i32,
175+
) -> world_position {
176+
let mut result = world_position::default();
177+
178+
result.ChunkY = (AbsTileY as u32 / TILES_PER_CHUNK).try_into().unwrap();
179+
result.ChunkX = (AbsTileX as u32 / TILES_PER_CHUNK).try_into().unwrap();
180+
result.ChunkZ = (AbsTileZ as u32 / TILES_PER_CHUNK).try_into().unwrap();
181+
182+
// TODO(casey): DECIDE ON TILE ALIGNMENT IN CHUNKS!
183+
result.Offset_.X = (AbsTileX - (result.ChunkX as u32 * TILES_PER_CHUNK) as i32) as f32
184+
* World.TileSideInMeters;
185+
result.Offset_.Y = (AbsTileY - (result.ChunkY as u32 * TILES_PER_CHUNK) as i32) as f32
186+
* World.TileSideInMeters;
187+
// TODO(casey): Move to 3D Z!!!
188+
189+
return result;
190+
}
191+
192+
pub fn Subtract(World: &world, A: &world_position, B: &world_position) -> world_difference {
193+
let mut result = world_difference::default();
194+
195+
let dTileXY = v2 {
196+
X: A.ChunkX as f32 - B.ChunkX as f32,
197+
Y: A.ChunkY as f32 - B.ChunkY as f32,
198+
};
199+
let dTileZ = A.ChunkZ - B.ChunkZ;
200+
201+
result.dXY = World.ChunkSideInMeters * dTileXY + (A.Offset_ - B.Offset_);
202+
// TODO(casey): Think about what we want to do about Z
203+
result.dZ = World.ChunkSideInMeters * dTileZ as f32;
204+
205+
return result;
206+
}
207+
208+
pub fn CenteredChunkPoint(ChunkX: u32, ChunkY: u32, ChunkZ: u32) -> world_position {
209+
let mut result = world_position::default();
210+
211+
result.ChunkX = ChunkX.try_into().unwrap();
212+
result.ChunkY = ChunkY.try_into().unwrap();
213+
result.ChunkZ = ChunkZ.try_into().unwrap();
214+
215+
return result;
216+
}
217+
218+
pub fn ChangeEntityLocation(
219+
Arena: &mut memory_arena,
220+
World: &mut world,
221+
LowEntityIndex: u32,
222+
OldP: Option<&world_position>,
223+
NewP: &world_position,
224+
) {
225+
if OldP.is_some() && AreInSameChunk(World, OldP.unwrap(), NewP) {
226+
// NOTE(casey): Leave entity where it is
227+
} else {
228+
if OldP.is_some() {
229+
let OldP = OldP.unwrap();
230+
// NOTE(casey): Pull the entity out of its old entity block
231+
let Chunk = GetWorldChunk(
232+
&mut World.ChunkHash,
233+
OldP.ChunkX,
234+
OldP.ChunkY,
235+
OldP.ChunkZ,
236+
None,
237+
);
238+
unsafe {
239+
if !Chunk.is_null() {
240+
let Chunk = Chunk;
241+
let mut NotFound = true;
242+
let mut FirstBlock = (*Chunk).FirstBlock.as_mut().unwrap();
243+
244+
let mut Block = FirstBlock as *mut world_entity_block;
245+
246+
while !Block.is_null() && NotFound {
247+
{
248+
let mut Block = *Block;
249+
let mut Index = 0;
250+
while (Index < Block.EntityCount) && NotFound {
251+
if Block.LowEntityIndex[Index as usize] == LowEntityIndex {
252+
FirstBlock.EntityCount -= 1;
253+
Block.LowEntityIndex[Index as usize] =
254+
FirstBlock.LowEntityIndex[FirstBlock.EntityCount as usize];
255+
if FirstBlock.EntityCount == 0 {
256+
if FirstBlock.Next.is_some() {
257+
let NextBlock = FirstBlock.Next.unwrap();
258+
*FirstBlock = *NextBlock;
259+
(*NextBlock).Next =
260+
Some(*World.FirstFree.as_mut().unwrap()
261+
as *mut world_entity_block);
262+
World.FirstFree = Some(&mut *NextBlock);
263+
}
264+
}
265+
266+
NotFound = false;
267+
}
268+
Index += 1;
269+
}
270+
}
271+
if (*Block).Next.is_some() {
272+
Block = (*Block).Next.unwrap();
273+
} else {
274+
Block = null_mut();
275+
}
276+
}
277+
}
278+
}
279+
}
280+
281+
// NOTE(casey): Insert the entity into its new entity block
282+
let Chunk = GetWorldChunk(
283+
&mut World.ChunkHash,
284+
NewP.ChunkX,
285+
NewP.ChunkY,
286+
NewP.ChunkZ,
287+
Some(Arena),
288+
);
289+
unsafe {
290+
let mut Block = (*Chunk).FirstBlock.unwrap();
291+
if Block.EntityCount == (Block.LowEntityIndex.len()).try_into().unwrap() {
292+
// NOTE(casey): We're out of room, get a new block!
293+
let mut OldBlock = World.FirstFree;
294+
if OldBlock.is_some() {
295+
World.FirstFree = (*OldBlock.unwrap()).Next;
296+
} else {
297+
OldBlock = Some(PushStruct::<world_entity_block>(Arena));
298+
}
299+
let OldBlock = World.FirstFree.unwrap();
300+
*OldBlock = Block;
301+
Block.Next = Some(OldBlock);
302+
Block.EntityCount = 0;
303+
}
304+
305+
Block.LowEntityIndex[Block.EntityCount as usize] = LowEntityIndex;
306+
Block.EntityCount += 1; //MIGHT BE WRONG
307+
}
308+
}
309+
}

‎src/win32_handmade.rs

-1
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,6 @@ unsafe fn win32_display_buffer_in_window(
841841
buffer: &Win32OffScreenBuffer,
842842
) {
843843
if (window_width >= buffer.width * 2) && (window_height >= buffer.height * 2) {
844-
dbg!("RUNNING");
845844
StretchDIBits(
846845
device_context,
847846
0,

0 commit comments

Comments
 (0)
Please sign in to comment.