Compare commits
No commits in common. '7e0dfd8fddb1cc3a71fb8de44264849ba014c6ba' and '536ff0ebb6031699bcf5c26d86bb09d4849703b7' have entirely different histories.
7e0dfd8fdd
...
536ff0ebb6
21 changed files with 90 additions and 567 deletions
@ -1,3 +0,0 @@ |
|||||||
( |
|
||||||
color: (124, 144, 255), |
|
||||||
) |
|
||||||
@ -1,3 +0,0 @@ |
|||||||
( |
|
||||||
color: (64, 64, 64), |
|
||||||
) |
|
||||||
@ -0,0 +1,38 @@ |
|||||||
|
use bevy::prelude::*; |
||||||
|
use common::prelude::*; |
||||||
|
|
||||||
|
pub fn plugin(app: &mut App) { |
||||||
|
app.load_resource::<BlockAssets>(); |
||||||
|
app.add_observer(insert_block_visuals); |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Resource, Asset, Reflect, Clone)] |
||||||
|
#[reflect(Resource)] |
||||||
|
pub struct BlockAssets { |
||||||
|
#[dependency] |
||||||
|
mesh: Handle<Mesh>, |
||||||
|
#[dependency] |
||||||
|
material: Handle<StandardMaterial>, |
||||||
|
} |
||||||
|
|
||||||
|
impl FromWorld for BlockAssets { |
||||||
|
fn from_world(world: &mut World) -> Self { |
||||||
|
let assets = world.resource::<AssetServer>(); |
||||||
|
Self { |
||||||
|
mesh: assets.add(Cuboid::new(1.0, 1.0, 1.0).into()), |
||||||
|
material: assets.add(Color::srgb_u8(124, 144, 255).into()), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn insert_block_visuals( |
||||||
|
event: On<Add, Block>, |
||||||
|
block_assets: Res<BlockAssets>, |
||||||
|
mut commands: Commands, |
||||||
|
) { |
||||||
|
let block = event.entity; |
||||||
|
commands.entity(block).insert(( |
||||||
|
Mesh3d(block_assets.mesh.clone()), |
||||||
|
MeshMaterial3d(block_assets.material.clone()), |
||||||
|
)); |
||||||
|
} |
||||||
@ -1,122 +0,0 @@ |
|||||||
use bevy::prelude::*; |
|
||||||
use common::prelude::*; |
|
||||||
|
|
||||||
use bevy::asset::{AssetLoader, LoadContext, io::Reader}; |
|
||||||
use serde::Deserialize; |
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) { |
|
||||||
let mesh = Mesh::from(Cuboid::new(1.0, 1.0, 1.0)); |
|
||||||
let cube = app.world_mut().add_asset(mesh); |
|
||||||
app.insert_resource(BuiltinBlockMeshes { cube }); |
|
||||||
|
|
||||||
app.init_asset::<BlockVisuals>(); |
|
||||||
app.init_asset_loader::<BlockVisualsLoader>(); |
|
||||||
app.load_resource::<BlockVisualsCollection>(); |
|
||||||
|
|
||||||
app.add_observer(insert_block_visuals); |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Resource)] |
|
||||||
struct BuiltinBlockMeshes { |
|
||||||
cube: Handle<Mesh>, |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Asset, TypePath)] |
|
||||||
struct BlockVisuals { |
|
||||||
_id: Identifier<Block>, |
|
||||||
// mesh: Handle<Mesh>,
|
|
||||||
material: Handle<StandardMaterial>, |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Deserialize)] |
|
||||||
struct BlockTypeVisualsRaw { |
|
||||||
color: (u8, u8, u8), |
|
||||||
// ignore unknown fields
|
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Default)] |
|
||||||
struct BlockVisualsLoader; |
|
||||||
|
|
||||||
impl AssetLoader for BlockVisualsLoader { |
|
||||||
type Asset = BlockVisuals; |
|
||||||
type Settings = (); |
|
||||||
type Error = BevyError; |
|
||||||
|
|
||||||
async fn load( |
|
||||||
&self, |
|
||||||
reader: &mut dyn Reader, |
|
||||||
_settings: &Self::Settings, |
|
||||||
load_context: &mut LoadContext<'_>, |
|
||||||
) -> Result<Self::Asset, Self::Error> { |
|
||||||
let mut bytes = Vec::new(); |
|
||||||
reader.read_to_end(&mut bytes).await?; |
|
||||||
let raw = ron::de::from_bytes::<BlockTypeVisualsRaw>(&bytes)?; |
|
||||||
|
|
||||||
let (r, g, b) = raw.color; |
|
||||||
let material = StandardMaterial::from(Color::srgb_u8(r, g, b)); |
|
||||||
let material = load_context.add_labeled_asset("material".to_string(), material); |
|
||||||
|
|
||||||
// TODO: Figure out how to reference a procedural mesh from here.
|
|
||||||
// let mesh = load_context.load(???);
|
|
||||||
|
|
||||||
let id = load_context.path().try_into()?; |
|
||||||
Ok(BlockVisuals { _id: id, material }) |
|
||||||
} |
|
||||||
|
|
||||||
fn extensions(&self) -> &[&str] { |
|
||||||
&["ron"] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Resource, Asset, Reflect, Clone)] |
|
||||||
#[reflect(Resource)] |
|
||||||
struct BlockVisualsCollection { |
|
||||||
#[dependency] |
|
||||||
default: Handle<BlockVisuals>, |
|
||||||
#[dependency] |
|
||||||
platform: Handle<BlockVisuals>, |
|
||||||
} |
|
||||||
|
|
||||||
impl BlockVisualsCollection { |
|
||||||
pub fn get(&self, id: &Identifier<Block>) -> Option<&Handle<BlockVisuals>> { |
|
||||||
match id.as_ref() { |
|
||||||
"default" => Some(&self.default), |
|
||||||
"platform" => Some(&self.platform), |
|
||||||
_ => None, |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl FromWorld for BlockVisualsCollection { |
|
||||||
fn from_world(world: &mut World) -> Self { |
|
||||||
let assets = world.resource::<AssetServer>(); |
|
||||||
Self { |
|
||||||
default: assets.load("blocks/default.ron"), |
|
||||||
platform: assets.load("blocks/platform.ron"), |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn insert_block_visuals( |
|
||||||
event: On<Add, Block>, |
|
||||||
blocks: Query<&Block>, |
|
||||||
visuals_collection: Res<BlockVisualsCollection>, |
|
||||||
visuals_assets: Res<Assets<BlockVisuals>>, |
|
||||||
meshes: Res<BuiltinBlockMeshes>, |
|
||||||
mut commands: Commands, |
|
||||||
) { |
|
||||||
let block = event.entity; |
|
||||||
let id = blocks.get(block).unwrap().id(); |
|
||||||
|
|
||||||
let Some(visuals) = visuals_collection.get(id) else { |
|
||||||
warn!("missing BlockVisuals for id `{id}`"); |
|
||||||
return; |
|
||||||
}; |
|
||||||
// SAFETY: If it's in `BlockVisualsCollection`, it should be in `Assets<BlockVisuals>`.
|
|
||||||
let visuals = visuals_assets.get(visuals).unwrap(); |
|
||||||
|
|
||||||
commands.entity(block).insert(( |
|
||||||
Mesh3d(meshes.cube.clone()), |
|
||||||
MeshMaterial3d(visuals.material.clone()), |
|
||||||
)); |
|
||||||
} |
|
||||||
@ -1,88 +0,0 @@ |
|||||||
use bevy::prelude::*; |
|
||||||
|
|
||||||
use bevy::asset::{AssetLoader, LoadContext, io::Reader}; |
|
||||||
use serde::Deserialize; |
|
||||||
|
|
||||||
use crate::assets::asset_tracking::LoadResource; |
|
||||||
use crate::block::Block; |
|
||||||
use crate::prelude::Identifier; |
|
||||||
|
|
||||||
pub(super) fn plugin(app: &mut App) { |
|
||||||
app.init_asset::<BlockDefinition>(); |
|
||||||
app.init_asset_loader::<BlockDefinitionLoader>(); |
|
||||||
app.load_resource::<BlockDefinitionCollection>(); |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Asset, TypePath)] |
|
||||||
pub struct BlockDefinition { |
|
||||||
id: Identifier<Block>, |
|
||||||
} |
|
||||||
|
|
||||||
impl BlockDefinition { |
|
||||||
pub fn id(&self) -> &Identifier<Block> { |
|
||||||
&self.id |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Deserialize)] |
|
||||||
struct BlockDefinitionRaw { |
|
||||||
// ignore unknown fields
|
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Default)] |
|
||||||
struct BlockDefinitionLoader; |
|
||||||
|
|
||||||
impl AssetLoader for BlockDefinitionLoader { |
|
||||||
type Asset = BlockDefinition; |
|
||||||
type Settings = (); |
|
||||||
type Error = BevyError; |
|
||||||
|
|
||||||
async fn load( |
|
||||||
&self, |
|
||||||
reader: &mut dyn Reader, |
|
||||||
_settings: &Self::Settings, |
|
||||||
load_context: &mut LoadContext<'_>, |
|
||||||
) -> Result<Self::Asset, Self::Error> { |
|
||||||
let mut bytes = Vec::new(); |
|
||||||
reader.read_to_end(&mut bytes).await?; |
|
||||||
// NOTE: Currently unused, because `BlockDefinition` doesn't have any properties.
|
|
||||||
// So this basically just makes sure the file parses correctly.
|
|
||||||
let _raw = ron::de::from_bytes::<BlockDefinitionRaw>(&bytes)?; |
|
||||||
|
|
||||||
let id = load_context.path().try_into()?; |
|
||||||
Ok(BlockDefinition { id }) |
|
||||||
} |
|
||||||
|
|
||||||
fn extensions(&self) -> &[&str] { |
|
||||||
&["ron"] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Resource, Asset, Reflect, Clone)] |
|
||||||
#[reflect(Resource)] |
|
||||||
pub struct BlockDefinitionCollection { |
|
||||||
#[dependency] |
|
||||||
default: Handle<BlockDefinition>, |
|
||||||
#[dependency] |
|
||||||
platform: Handle<BlockDefinition>, |
|
||||||
} |
|
||||||
|
|
||||||
impl BlockDefinitionCollection { |
|
||||||
pub fn get(&self, id: &Identifier<Block>) -> Option<&Handle<BlockDefinition>> { |
|
||||||
match id.as_ref() { |
|
||||||
"default" => Some(&self.default), |
|
||||||
"platform" => Some(&self.platform), |
|
||||||
_ => None, |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl FromWorld for BlockDefinitionCollection { |
|
||||||
fn from_world(world: &mut World) -> Self { |
|
||||||
let assets = world.resource::<AssetServer>(); |
|
||||||
Self { |
|
||||||
default: assets.load("blocks/default.ron"), |
|
||||||
platform: assets.load("blocks/platform.ron"), |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
use bevy::prelude::*; |
|
||||||
|
|
||||||
pub mod asset_tracking; |
|
||||||
pub mod block_definition; |
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) { |
|
||||||
// `tracking::plugin` must come first.
|
|
||||||
app.add_plugins(asset_tracking::plugin); |
|
||||||
|
|
||||||
app.add_plugins(block_definition::plugin); |
|
||||||
} |
|
||||||
@ -1,113 +0,0 @@ |
|||||||
use std::borrow::Cow; |
|
||||||
use std::marker::PhantomData; |
|
||||||
use std::path::Path; |
|
||||||
|
|
||||||
use bevy::prelude::*; |
|
||||||
|
|
||||||
use derive_where::derive_where; |
|
||||||
use thiserror::Error; |
|
||||||
|
|
||||||
#[derive(Reflect, Deref)] |
|
||||||
#[derive_where(Deserialize, Serialize, Clone, PartialEq, Eq, Hash, Debug)] |
|
||||||
#[serde(try_from = "String", into = "String")] |
|
||||||
pub struct Identifier<T> { |
|
||||||
#[deref] |
|
||||||
value: Cow<'static, str>, |
|
||||||
#[reflect(ignore)] |
|
||||||
identifier_type: PhantomData<T>, |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> Identifier<T> { |
|
||||||
pub const unsafe fn new_unsafe(value: Cow<'static, str>) -> Self { |
|
||||||
Self { |
|
||||||
value, |
|
||||||
identifier_type: PhantomData, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub fn new(value: impl Into<String>) -> Result<Self, IdentifierParseError> { |
|
||||||
let value = value.into(); |
|
||||||
Self::validate(&value)?; |
|
||||||
Ok(unsafe { Self::new_unsafe(Cow::Owned(value)) }) |
|
||||||
} |
|
||||||
|
|
||||||
pub const fn new_const(value: &'static str) -> Self { |
|
||||||
match Self::validate(&value) { |
|
||||||
Ok(_) => unsafe { Self::new_unsafe(Cow::Borrowed(value)) }, |
|
||||||
Err(_) => panic!("invalid identifier"), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const fn validate(value: &str) -> Result<(), IdentifierParseError> { |
|
||||||
if value.is_empty() { |
|
||||||
return Err(IdentifierParseError::Empty); |
|
||||||
} |
|
||||||
if value.len() > 32 { |
|
||||||
return Err(IdentifierParseError::Length); |
|
||||||
} |
|
||||||
// if value.chars().any(|c| !c.is_ascii_lowercase())
|
|
||||||
// NOTE: This mess is to allow the function to be called in a const context.
|
|
||||||
let bytes = value.as_bytes(); |
|
||||||
let mut i = 0; |
|
||||||
while i < value.len() { |
|
||||||
if let Some(c) = char::from_u32(bytes[i] as u32) { |
|
||||||
if c.is_ascii_lowercase() { |
|
||||||
i += 1; |
|
||||||
continue; |
|
||||||
} |
|
||||||
} |
|
||||||
return Err(IdentifierParseError::Invalid); |
|
||||||
} |
|
||||||
Ok(()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Error, Debug)] |
|
||||||
pub enum IdentifierParseError { |
|
||||||
#[error("String is empty")] |
|
||||||
Empty, |
|
||||||
#[error("String is too long")] |
|
||||||
Length, |
|
||||||
#[error("String contains invalid characters")] |
|
||||||
Invalid, |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> std::fmt::Display for Identifier<T> { |
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|
||||||
write!(f, "{}", self.value) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> From<Identifier<T>> for String { |
|
||||||
fn from(value: Identifier<T>) -> Self { |
|
||||||
value.to_string() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> TryFrom<String> for Identifier<T> { |
|
||||||
type Error = IdentifierParseError; |
|
||||||
fn try_from(value: String) -> std::result::Result<Self, Self::Error> { |
|
||||||
Self::new(value) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> TryFrom<&Path> for Identifier<T> { |
|
||||||
type Error = IdentifierParseError; |
|
||||||
fn try_from(value: &Path) -> Result<Self, Self::Error> { |
|
||||||
// SAFETY: Since this is an asset path, it should never fail.
|
|
||||||
let file_name = value.file_name().unwrap(); |
|
||||||
// SAFETY: If the asset path contains invalid UTF-8, it's someone else's fault.
|
|
||||||
let file_name_str = file_name.to_str().expect("invalid utf8"); |
|
||||||
|
|
||||||
let id = if let Some((prefix, _)) = file_name_str.split_once('.') { |
|
||||||
if prefix.is_empty() { |
|
||||||
panic!("asset file name starts with dot") |
|
||||||
} |
|
||||||
prefix |
|
||||||
} else { |
|
||||||
file_name_str |
|
||||||
}; |
|
||||||
|
|
||||||
Self::new(id) |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue