initial commit & start building up ecs

This commit is contained in:
Vivian Lim 2023-01-24 02:02:57 -08:00
commit 681d1b79ac
10 changed files with 563 additions and 0 deletions

15
.cargo/config.toml Normal file
View File

@ -0,0 +1,15 @@
[build]
target = "wasm32-unknown-unknown"
[target.wasm32-unknown-unknown]
rustflags = [
# Import memory from WASM-4
"-C", "link-arg=--import-memory",
"-C", "link-arg=--initial-memory=65536",
"-C", "link-arg=--max-memory=65536",
# Temporary workaround for #255 issue.
# Reserve 8192 bytes of Rust stack space, offset from 6560.
# Bump this value, 16-byte aligned, if the framebuffer gets corrupted.
"-C", "link-arg=-zstack-size=14752",
]

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

6
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
// 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",
}

86
Cargo.lock generated Normal file
View File

@ -0,0 +1,86 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "buddy-alloc"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff9f338986406db85e2b5deb40a9255b796ca03a194c7457403d215173f3fd5"
[[package]]
name = "cart"
version = "0.1.0"
dependencies = [
"buddy-alloc",
"strum",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "cart"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
buddy-alloc = { version = "0.4.1", optional = true }
strum = { version = "0.24", features = ["derive"], default-features = false }
[profile.release]
opt-level = "z"
lto = true
[features]
# use `--no-default-features` or comment out next line to disable allocator
default = ["buddy-alloc"]

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# thang
A game written in Rust for the [WASM-4](https://wasm4.org) fantasy console.
## Building
Build the cart by running:
```shell
cargo build --release
```
Then run it with:
```shell
w4 run target/wasm32-unknown-unknown/release/cart.wasm
```
For more info about setting up WASM-4, see the [quickstart guide](https://wasm4.org/docs/getting-started/setup?code-lang=rust#quickstart).
## Links
- [Documentation](https://wasm4.org/docs): Learn more about WASM-4.
- [Snake Tutorial](https://wasm4.org/docs/tutorials/snake/goal): Learn how to build a complete game
with a step-by-step tutorial.
- [GitHub](https://github.com/aduros/wasm4): Submit an issue or PR. Contributions are welcome!

123
src/ecs.rs Normal file
View File

@ -0,0 +1,123 @@
use strum::{EnumDiscriminants, EnumCount, EnumIter};
use alloc::vec::Vec;
use crate::wasm4::trace;
pub struct Scene {
entities: Vec<Option<Entity>>,
components: Vec<Option<ComponentRecord>>,
unused_entity_ids: Vec<EntityId>,
unused_component_ids: Vec<ComponentId>
}
struct ComponentRecord {
component: Component,
owning_entity: EntityId,
}
impl Scene {
pub fn new() -> Scene {
Scene {
entities: Vec::with_capacity(32),
components: Vec::with_capacity(128),
unused_entity_ids: Vec::new(),
unused_component_ids: Vec::new()
}
}
pub fn add_entity(&mut self){
}
pub fn query_entities<const N: usize>(&self, query: &[ComponentDiscriminants; N]) -> Vec<(EntityId, [ComponentId; N])> {
let mut result = Vec::new();
for entity_index in 0..self.entities.len() {
if let Some(entity) = &self.entities[entity_index] {
if let Some(component_ids) = entity.component_ids(query) {
result.push((EntityId(entity_index), component_ids));
}
}
}
return result;
}
pub fn borrow_component_ids<const N: usize>(&mut self, ids: &[ComponentId; N]) -> [Option<&Component>; N] {
let mut result = [None; N];
for id_index in 0..ids.len() {
let ComponentId(id) = ids[id_index];
if id >= self.components.len() {
trace(format!("tried to borrow a component with id {} when there are {} components", id, self.components.len()));
}
match &self.components[id] {
Some(ComponentRecord { component, owning_entity }) => {
result[id_index] = Some(component);
},
None => trace(format!("tried to borrow a component with id {} but it has been removed.", id))
}
}
result.into()
}
pub unsafe fn mut_borrow_component_unsafely(&self, id: &ComponentId) -> Option<&Component> {
let ComponentId(id) = *id;
if id >= self.components.len(){
trace(format!("tried to mutably borrow a component with id {} when there are {} components", id, self.components.len()));
return None;
}
match &mut self.components[id] {
Some(ComponentRecord { component, owning_entity }) => {
let component_ptr = component as *const Component;
let component_mut_ptr = component_ptr as *mut T;
return Some(&mut *component_mut_ptr);
},
None => trace(format!("tried to mutably borrow a component with id {} but it has been removed.", id))
}
return None;
}
}
#[derive(Debug, Clone, Copy)]
pub struct EntityId(usize);
#[derive(Debug, Clone, Copy)]
pub struct ComponentId(usize);
pub struct Entity {
components: [Option<usize>; Component::COUNT]
}
impl Entity {
pub fn new() -> Entity {
Entity {
components: [None; Component::COUNT]
}
}
pub fn component_ids<const N: usize>(&self, query: &[ComponentDiscriminants; N]) -> Option<[ComponentId; N]>{
let mut result: [ComponentId; N] = [ComponentId(0); N];
for query_index in 0..query.len() {
let discriminant = query[query_index];
if let Some(id) = self.components[discriminant as usize] {
result[query_index] = ComponentId(id);
}
else {
return None;
}
}
return Some(result);
}
}
#[repr(usize)]
#[derive(EnumDiscriminants, EnumCount, EnumIter)]
#[strum_discriminants(repr(usize))]
pub enum Component {
Name(&'static str),
Position { x: u8, y: u8 }
}

45
src/lib.rs Normal file
View File

@ -0,0 +1,45 @@
#![cfg_attr(not(test), no_std)]
#[cfg(feature = "buddy-alloc")]
mod my_alloc;
mod wasm4;
use core::panic::PanicInfo;
#[macro_use]
extern crate alloc;
use wasm4::*;
mod ecs;
#[rustfmt::skip]
const SMILEY: [u8; 8] = [
0b11000011,
0b10000001,
0b00100100,
0b00100100,
0b00000000,
0b00100100,
0b10011001,
0b11000011,
];
#[cfg_attr(not(test), panic_handler)]
fn panic(info: &PanicInfo) -> ! {
trace(format!("{}", info));
loop {}
}
#[no_mangle]
fn update() {
unsafe { *DRAW_COLORS = 2 }
text("Hello from Rust!", 10, 10);
let gamepad = unsafe { *GAMEPAD1 };
if gamepad & BUTTON_1 != 0 {
unsafe { *DRAW_COLORS = 4 }
}
blit(&SMILEY, 76, 76, 8, 8, BLIT_1BPP);
text("Press X to blink", 16, 90);
}

16
src/my_alloc.rs Normal file
View File

@ -0,0 +1,16 @@
use buddy_alloc::{BuddyAllocParam, FastAllocParam, NonThreadsafeAlloc};
// These values can be tuned
const FAST_HEAP_SIZE: usize = 4 * 1024; // 4 KB
const HEAP_SIZE: usize = 16 * 1024; // 16 KB
const LEAF_SIZE: usize = 16;
static mut FAST_HEAP: [u8; FAST_HEAP_SIZE] = [0u8; FAST_HEAP_SIZE];
static mut HEAP: [u8; HEAP_SIZE] = [0u8; HEAP_SIZE];
#[global_allocator]
static ALLOC: NonThreadsafeAlloc = unsafe {
let fast_param = FastAllocParam::new(FAST_HEAP.as_ptr(), FAST_HEAP_SIZE);
let buddy_param = BuddyAllocParam::new(HEAP.as_ptr(), HEAP_SIZE, LEAF_SIZE);
NonThreadsafeAlloc::new(fast_param, buddy_param)
};

225
src/wasm4.rs Normal file
View File

@ -0,0 +1,225 @@
//
// WASM-4: https://wasm4.org/docs
#![allow(unused)]
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ Platform Constants │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘
pub const SCREEN_SIZE: u32 = 160;
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ Memory Addresses │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘
pub const PALETTE: *mut [u32; 4] = 0x04 as *mut [u32; 4];
pub const DRAW_COLORS: *mut u16 = 0x14 as *mut u16;
pub const GAMEPAD1: *const u8 = 0x16 as *const u8;
pub const GAMEPAD2: *const u8 = 0x17 as *const u8;
pub const GAMEPAD3: *const u8 = 0x18 as *const u8;
pub const GAMEPAD4: *const u8 = 0x19 as *const u8;
pub const MOUSE_X: *const i16 = 0x1a as *const i16;
pub const MOUSE_Y: *const i16 = 0x1c as *const i16;
pub const MOUSE_BUTTONS: *const u8 = 0x1e as *const u8;
pub const SYSTEM_FLAGS: *mut u8 = 0x1f as *mut u8;
pub const NETPLAY: *const u8 = 0x20 as *const u8;
pub const FRAMEBUFFER: *mut [u8; 6400] = 0xa0 as *mut [u8; 6400];
pub const BUTTON_1: u8 = 1;
pub const BUTTON_2: u8 = 2;
pub const BUTTON_LEFT: u8 = 16;
pub const BUTTON_RIGHT: u8 = 32;
pub const BUTTON_UP: u8 = 64;
pub const BUTTON_DOWN: u8 = 128;
pub const MOUSE_LEFT: u8 = 1;
pub const MOUSE_RIGHT: u8 = 2;
pub const MOUSE_MIDDLE: u8 = 4;
pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1;
pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2;
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ Drawing Functions │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘
/// Copies pixels to the framebuffer.
pub fn blit(sprite: &[u8], x: i32, y: i32, width: u32, height: u32, flags: u32) {
unsafe { extern_blit(sprite.as_ptr(), x, y, width, height, flags) }
}
extern "C" {
#[link_name = "blit"]
fn extern_blit(sprite: *const u8, x: i32, y: i32, width: u32, height: u32, flags: u32);
}
/// Copies a subregion within a larger sprite atlas to the framebuffer.
#[allow(clippy::too_many_arguments)]
pub fn blit_sub(
sprite: &[u8],
x: i32,
y: i32,
width: u32,
height: u32,
src_x: u32,
src_y: u32,
stride: u32,
flags: u32,
) {
unsafe {
extern_blit_sub(
sprite.as_ptr(),
x,
y,
width,
height,
src_x,
src_y,
stride,
flags,
)
}
}
extern "C" {
#[link_name = "blitSub"]
fn extern_blit_sub(
sprite: *const u8,
x: i32,
y: i32,
width: u32,
height: u32,
src_x: u32,
src_y: u32,
stride: u32,
flags: u32,
);
}
pub const BLIT_2BPP: u32 = 1;
pub const BLIT_1BPP: u32 = 0;
pub const BLIT_FLIP_X: u32 = 2;
pub const BLIT_FLIP_Y: u32 = 4;
pub const BLIT_ROTATE: u32 = 8;
/// Draws a line between two points.
pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) {
unsafe { extern_line(x1, y1, x2, y2) }
}
extern "C" {
#[link_name = "line"]
fn extern_line(x1: i32, y1: i32, x2: i32, y2: i32);
}
/// Draws an oval (or circle).
pub fn oval(x: i32, y: i32, width: u32, height: u32) {
unsafe { extern_oval(x, y, width, height) }
}
extern "C" {
#[link_name = "oval"]
fn extern_oval(x: i32, y: i32, width: u32, height: u32);
}
/// Draws a rectangle.
pub fn rect(x: i32, y: i32, width: u32, height: u32) {
unsafe { extern_rect(x, y, width, height) }
}
extern "C" {
#[link_name = "rect"]
fn extern_rect(x: i32, y: i32, width: u32, height: u32);
}
/// Draws text using the built-in system font.
pub fn text<T: AsRef<[u8]>>(text: T, x: i32, y: i32) {
let text_ref = text.as_ref();
unsafe { extern_text(text_ref.as_ptr(), text_ref.len(), x, y) }
}
extern "C" {
#[link_name = "textUtf8"]
fn extern_text(text: *const u8, length: usize, x: i32, y: i32);
}
/// Draws a vertical line
pub fn vline(x: i32, y: i32, len: u32) {
unsafe {
extern_vline(x, y, len);
}
}
extern "C" {
#[link_name = "vline"]
fn extern_vline(x: i32, y: i32, len: u32);
}
/// Draws a horizontal line
pub fn hline(x: i32, y: i32, len: u32) {
unsafe {
extern_hline(x, y, len);
}
}
extern "C" {
#[link_name = "hline"]
fn extern_hline(x: i32, y: i32, len: u32);
}
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ Sound Functions │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘
/// Plays a sound tone.
pub fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) {
unsafe { extern_tone(frequency, duration, volume, flags) }
}
extern "C" {
#[link_name = "tone"]
fn extern_tone(frequency: u32, duration: u32, volume: u32, flags: u32);
}
pub const TONE_PULSE1: u32 = 0;
pub const TONE_PULSE2: u32 = 1;
pub const TONE_TRIANGLE: u32 = 2;
pub const TONE_NOISE: u32 = 3;
pub const TONE_MODE1: u32 = 0;
pub const TONE_MODE2: u32 = 4;
pub const TONE_MODE3: u32 = 8;
pub const TONE_MODE4: u32 = 12;
pub const TONE_PAN_LEFT: u32 = 16;
pub const TONE_PAN_RIGHT: u32 = 32;
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ Storage Functions │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘
extern "C" {
/// Reads up to `size` bytes from persistent storage into the pointer `dest`.
pub fn diskr(dest: *mut u8, size: u32) -> u32;
/// Writes up to `size` bytes from the pointer `src` into persistent storage.
pub fn diskw(src: *const u8, size: u32) -> u32;
}
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ Other Functions │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘
/// Prints a message to the debug console.
pub fn trace<T: AsRef<str>>(text: T) {
let text_ref = text.as_ref();
unsafe { extern_trace(text_ref.as_ptr(), text_ref.len()) }
}
extern "C" {
#[link_name = "traceUtf8"]
fn extern_trace(trace: *const u8, length: usize);
}