#![allow(deprecated)] use std::sync::Mutex; use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, reflect::Reflect, render::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::ImageSampler, }, window::{PrimaryWindow, WindowResized}, }; use bevy_mod_scripting::prelude::*; #[derive(Debug, Default, Clone, Reflect, Component, LuaProxy)] #[reflect(Component, LuaProxyable)] pub struct LifeState { pub cells: Vec, } #[derive(Default)] pub struct LifeAPI; impl APIProvider for LifeAPI { type APITarget = Mutex; type ScriptContext = Mutex; type DocTarget = LuaDocFragment; fn attach_api(&mut self, _: &mut Self::APITarget) -> Result<(), ScriptError> { // we don't actually provide anything global Ok(()) } fn register_with_app(&self, app: &mut App) { // this will register the `LuaProxyable` typedata since we derived it // this will resolve retrievals of this component to our custom lua object app.register_type::(); app.register_type::(); } } #[derive(Reflect, Resource)] #[reflect(Resource)] pub struct Settings { physical_grid_dimensions: (u32, u32), display_grid_dimensions: (u32, u32), border_thickness: u32, live_color: u8, dead_color: u8, } impl Default for Settings { fn default() -> Self { Self { border_thickness: 1, live_color: 255u8, dead_color: 0u8, physical_grid_dimensions: (88, 50), display_grid_dimensions: (0, 0), } } } pub fn setup( mut commands: Commands, mut assets: ResMut>, asset_server: Res, settings: Res, ) { let mut image = Image::new_fill( Extent3d { width: settings.physical_grid_dimensions.0, height: settings.physical_grid_dimensions.1, depth_or_array_layers: 1, }, TextureDimension::D2, &[0u8], TextureFormat::R8Unorm, RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD, ); image.sampler = ImageSampler::nearest(); let script_path = bevy_mod_scripting_lua::lua_path!("game_of_life"); commands.spawn(Camera2d); commands .spawn(Sprite { image: assets.add(image), custom_size: Some(Vec2::new( settings.display_grid_dimensions.0 as f32, settings.display_grid_dimensions.1 as f32, )), color: Color::srgb(1.0, 0.388, 0.278), // TOMATO ..Default::default() }) .insert(LifeState { cells: vec![ 0u8; (settings.physical_grid_dimensions.0 * settings.physical_grid_dimensions.1) as usize ], }) .insert(ScriptCollection:: { scripts: vec![Script::new( script_path.to_owned(), asset_server.load(script_path), )], }); } pub fn sync_window_size( mut resize_event: EventReader, mut settings: ResMut, mut query: Query<&mut Sprite, With>, primary_windows: Query<&Window, With>, ) { if let Some(e) = resize_event .read() .filter(|e| primary_windows.get(e.window).is_ok()) .last() { let primary_window = primary_windows.get(e.window).unwrap(); settings.display_grid_dimensions = ( primary_window.physical_width(), primary_window.physical_height(), ); // resize all game's of life, retain aspect ratio and fit the entire game in the window for mut sprite in query.iter_mut() { let scale = if settings.physical_grid_dimensions.0 > settings.physical_grid_dimensions.1 { // horizontal is longer settings.display_grid_dimensions.1 as f32 / settings.physical_grid_dimensions.1 as f32 } else { // vertical is longer settings.display_grid_dimensions.0 as f32 / settings.physical_grid_dimensions.0 as f32 }; sprite.custom_size = Some(Vec2::new( (settings.physical_grid_dimensions.0 as f32) * scale, (settings.physical_grid_dimensions.1 as f32) * scale, )); } } } /// Runs after LifeState components are updated, updates their rendered representation pub fn update_rendered_state( mut assets: ResMut>, query: Query<(&LifeState, &Sprite)>, ) { for (new_state, old_rendered_state) in query.iter() { let old_rendered_state = assets .get_mut(&old_rendered_state.image) .expect("World is not setup correctly"); old_rendered_state.data = new_state.cells.clone(); } } /// Sends events allowing scripts to drive update logic pub fn send_on_update(mut events: PriorityEventWriter>) { events.send( LuaEvent { hook_name: "on_update".to_owned(), args: (), recipients: Recipients::All, }, 1, ) } /// Sends initialization event pub fn send_init(mut events: PriorityEventWriter>) { events.send( LuaEvent { hook_name: "init".to_owned(), args: (), recipients: Recipients::All, }, 0, ) } const UPDATE_FREQUENCY: f32 = 1.0 / 60.0; fn main() -> std::io::Result<()> { let mut app = App::new(); app.add_plugins(DefaultPlugins) .insert_resource(Time::::from_seconds(UPDATE_FREQUENCY.into())) .add_plugins(LogDiagnosticsPlugin::default()) .add_plugins(FrameTimeDiagnosticsPlugin) .add_plugins(ScriptingPlugin) .init_resource::() .add_systems(Startup, setup) .add_systems(Startup, send_init) .add_systems(Update, sync_window_size) .add_systems(FixedUpdate, update_rendered_state.after(sync_window_size)) .add_systems(FixedUpdate, send_on_update.after(update_rendered_state)) .add_systems(FixedUpdate, script_event_handler::, 0, 1>) .add_script_host::>(PostUpdate) .add_api_provider::>(Box::new(LuaCoreBevyAPIProvider)) .add_api_provider::>(Box::new(LifeAPI)); app.run(); Ok(()) }