Working init version

This commit is contained in:
Piotr Siuszko 2024-05-03 10:55:56 +02:00
parent 0527e0ccaa
commit 5d1354d92e
5 changed files with 5236 additions and 17 deletions

4952
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,4 +16,12 @@ opt-level = 1
opt-level = 3
[dependencies]
bevy = "0.11"
bevy_mod_scripting = { git = "https://github.com/makspll/bevy_mod_scripting", features = [
"lua54", "lua",
"lua_script_api",
], rev ="c497b432b53e7d" }
bevy_mod_scripting_core = { git = "https://github.com/makspll/bevy_mod_scripting", rev ="c497b432b53e7d" }
bevy_mod_scripting_lua = { git = "https://github.com/makspll/bevy_mod_scripting", rev ="c497b432b53e7d" }
bevy_script_api = { git = "https://github.com/makspll/bevy_mod_scripting", rev ="c497b432b53e7d", features= ["lua"] }
bevy = { version = "0.13.1", features = ["multi-threaded"] }
rand = "0.8.5"

View File

@ -0,0 +1,4 @@
function on_event(id)
print(string.format("on_event, script_id: %d, Handling:", script_id))
print(string.format("\t-> id: %d", id))
end

View File

@ -0,0 +1,58 @@
math.randomseed(os.time())
function init()
local LifeState = world:get_type_by_name("LifeState")
local life_state = world:get_component(entity, LifeState)
local cells = life_state.cells
-- set some cells alive
for _ = 1, 10000 do
local index = math.random(#cells)
cells[index] = 255
end
end
function on_update()
local LifeState = world:get_type_by_name("LifeState")
local Settings = world:get_type_by_name("Settings")
local life_state = world:get_component(entity, LifeState)
-- note currently this is a copy of the cells, as of now the macro does not automatically support Vec<T> proxies by reference
local cells = life_state.cells
-- note that here we do not make use of LuaProxyable and just go off pure reflection
local settings = world:get_resource(Settings)
local dimensions = settings.physical_grid_dimensions
-- primitives are passed by value to lua, keep a hold of old state but turn 255's into 1's
local prev_state = {}
for k, v in pairs(cells) do
prev_state[k] = (not (v == 0)) and 1 or 0
end
for i = 1, (dimensions[1] * dimensions[2]) do
local north = prev_state[i - dimensions[1]] or 1
local south = prev_state[i + dimensions[1]] or 1
local east = prev_state[i + 1] or 1
local west = prev_state[i - 1] or 1
local northeast = prev_state[i - dimensions[1] + 1] or 1
local southeast = prev_state[i + dimensions[1] + 1] or 1
local northwest = prev_state[i - dimensions[1] - 1] or 1
local southwest = prev_state[i + dimensions[1] - 1] or 1
local neighbours = north + south + east + west
+ northeast + southeast + northwest + southwest
-- was dead and got 3 neighbours now
if prev_state[i] == 0 and neighbours == 3 then
cells[i] = 255
-- was alive and should die now
elseif prev_state[i] == 1 and ((neighbours < 2) or (neighbours > 3)) then
cells[i] = 0
end
end
-- propagate the updates
life_state.cells = cells
end

View File

@ -1,22 +1,219 @@
// Bevy code commonly triggers these lints and they may be important signals
// about code quality. They are sometimes hard to avoid though, and the CI
// workflow treats them as errors, so this allows them throughout the project.
// Feel free to delete this line.
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
#![allow(deprecated)]
use std::sync::Mutex;
use bevy::prelude::*;
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
reflect::Reflect,
render::{
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat},
texture::ImageSampler,
},
window::{PrimaryWindow, WindowResized},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
use bevy_mod_scripting::prelude::*;
#[derive(Debug, Default, Clone, Reflect, Component, LuaProxy)]
#[reflect(Component, LuaProxyable)]
pub struct LifeState {
pub cells: Vec<u8>,
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
#[derive(Default)]
pub struct LifeAPI;
impl APIProvider for LifeAPI {
type APITarget = Mutex<Lua>;
type ScriptContext = Mutex<Lua>;
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::<LifeState>();
app.register_type::<Settings>();
}
}
#[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<Assets<Image>>,
asset_server: Res<AssetServer>,
settings: Res<Settings>,
) {
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(Camera2dBundle::default());
commands.spawn(SpriteBundle {
texture: asset_server.load("ducky.png"),
..Default::default()
});
commands
.spawn(SpriteBundle {
texture: assets.add(image),
sprite: Sprite {
custom_size: Some(Vec2::new(
settings.display_grid_dimensions.0 as f32,
settings.display_grid_dimensions.1 as f32,
)),
color: Color::TOMATO,
..Default::default()
},
..Default::default()
})
.insert(LifeState {
cells: vec![
0u8;
(settings.physical_grid_dimensions.0 * settings.physical_grid_dimensions.1)
as usize
],
})
.insert(ScriptCollection::<LuaFile> {
scripts: vec![Script::new(
script_path.to_owned(),
asset_server.load(script_path),
)],
});
}
pub fn sync_window_size(
mut resize_event: EventReader<WindowResized>,
mut settings: ResMut<Settings>,
mut query: Query<&mut Sprite, With<LifeState>>,
primary_windows: Query<&Window, With<PrimaryWindow>>,
) {
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<Assets<Image>>,
query: Query<(&LifeState, &Handle<Image>)>,
) {
for (new_state, old_rendered_state) in query.iter() {
let old_rendered_state = assets
.get_mut(old_rendered_state)
.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<LuaEvent<()>>) {
events.send(
LuaEvent {
hook_name: "on_update".to_owned(),
args: (),
recipients: Recipients::All,
},
1,
)
}
/// Sends initialization event
pub fn send_init(mut events: PriorityEventWriter<LuaEvent<()>>) {
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::<Fixed>::from_seconds(UPDATE_FREQUENCY.into()))
.add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin)
.add_plugins(ScriptingPlugin)
.init_resource::<Settings>()
.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::<LuaScriptHost<()>, 0, 1>)
.add_script_host::<LuaScriptHost<()>>(PostUpdate)
.add_api_provider::<LuaScriptHost<()>>(Box::new(LuaCoreBevyAPIProvider))
.add_api_provider::<LuaScriptHost<()>>(Box::new(LifeAPI));
app.run();
Ok(())
}