Added Tick to TileMap

This commit is contained in:
Jonathan Johnson 2023-10-29 08:41:50 -07:00
parent 6acaf7ed1c
commit 04e5381187
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
7 changed files with 230 additions and 62 deletions

27
Cargo.lock generated
View file

@ -435,6 +435,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "etagere"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644"
dependencies = [
"euclid",
"svg_fmt",
]
[[package]]
name = "euclid"
version = "0.22.9"
@ -830,18 +840,17 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kludgine"
version = "0.1.0"
source = "git+https://github.com/khonsulabs/kludgine#713200714daa16a273eed9fcaee476613e894116"
dependencies = [
"ahash",
"alot",
"appit",
"bytemuck",
"cosmic-text",
"etagere",
"figures",
"image",
"lyon_tessellation",
"pollster",
"shelf-packer",
"smallvec",
"wgpu",
]
@ -1493,14 +1502,6 @@ dependencies = [
"syn",
]
[[package]]
name = "shelf-packer"
version = "0.1.0"
source = "git+https://github.com/khonsulabs/shelf-packer#06246cb4f5ccd473c1d8ae49c8c07f2f61798df4"
dependencies = [
"figures",
]
[[package]]
name = "slotmap"
version = "1.0.6"
@ -1578,6 +1579,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "svg_fmt"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
[[package]]
name = "swash"
version = "0.1.8"

View file

@ -13,7 +13,7 @@ alot = "0.3"
interner = "0.2.1"
# appit = { git = "https://github.com/khonsulabs/appit" }
# [patch."https://github.com/khonsulabs/kludgine"]
# kludgine = { path = "../kludgine2" }
[patch."https://github.com/khonsulabs/kludgine"]
kludgine = { path = "../kludgine2" }
# [patch."https://github.com/khonsulabs/figures"]
# figures = { path = "../figures" }

View file

@ -4,11 +4,9 @@ use gooey::kludgine::figures::units::Px;
use gooey::kludgine::figures::{Point, Rect, Size};
use gooey::kludgine::render::Renderer;
use gooey::kludgine::shapes::Shape;
use gooey::kludgine::tilemap::{
Object, ObjectId, ObjectLayer, TileKind, TileMapFocus, Tiles, TILE_SIZE,
};
use gooey::kludgine::tilemap::{Object, ObjectLayer, TileKind, TileMapFocus, Tiles, TILE_SIZE};
use gooey::kludgine::Color;
use gooey::widget::{EventHandling, HANDLED, UNHANDLED};
use gooey::tick::Tick;
use gooey::widgets::TileMap;
use gooey::{EventLoopError, Run};
@ -35,7 +33,7 @@ fn main() -> Result<(), EventLoopError> {
let myself = characters.push(Player {
color: Color::RED,
position: Point::new(TILE_SIZE * 1, TILE_SIZE * 1),
position: Point::new(TILE_SIZE.0 as f32, TILE_SIZE.0 as f32),
});
let layers = Dynamic::new((Tiles::new(8, 8, TILES), characters));
@ -45,37 +43,43 @@ fn main() -> Result<(), EventLoopError> {
layer: 1,
id: myself,
})
.on_key(move |key| handle_key(key, myself, &layers))
.tick(Tick::fps(60, move |elapsed, input| {
// println!("Ticking {input:?}");
let mut direction = Point::new(0., 0.);
if input.keys.contains(&Key::ArrowDown) {
direction.y += 1.0;
}
if input.keys.contains(&Key::ArrowUp) {
direction.y -= 1.0;
}
if input.keys.contains(&Key::ArrowRight) {
direction.x += 1.0;
}
if input.keys.contains(&Key::ArrowLeft) {
direction.x -= 1.0;
}
let one_second_movement = direction * TILE_SIZE.0 as f32;
layers.map_mut(|layers| {
layers.1[myself].position += Point::new(
one_second_movement.x * elapsed.as_secs_f32(),
one_second_movement.y * elapsed.as_secs_f32(),
)
});
}))
.run()
}
fn handle_key(
key: Key,
player: ObjectId,
layers: &Dynamic<(Tiles, ObjectLayer<Player>)>,
) -> EventHandling {
let offset = match key {
Key::ArrowDown => Point::new(Px(0), Px(1)),
Key::ArrowUp => Point::new(Px(0), Px(-1)),
Key::ArrowLeft => Point::new(Px(-1), Px(0)),
Key::ArrowRight => Point::new(Px(1), Px(0)),
_ => return UNHANDLED,
};
layers.map_mut(|layers| layers.1[player].position += offset);
HANDLED
}
#[derive(Debug)]
struct Player {
color: Color,
position: Point<Px>,
position: Point<f32>,
}
impl Object for Player {
fn position(&self) -> Point<Px> {
self.position
self.position.cast()
}
fn render(&self, center: Point<Px>, zoom: f32, context: &mut Renderer<'_, '_>) {

View file

@ -11,6 +11,7 @@ pub mod dynamic;
pub mod graphics;
pub mod names;
pub mod styles;
pub mod tick;
mod tree;
mod utils;
pub mod widget;

159
src/tick.rs Normal file
View file

@ -0,0 +1,159 @@
use std::collections::HashSet;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
use std::time::{Duration, Instant};
use kludgine::app::winit::event::KeyEvent;
use kludgine::app::winit::keyboard::Key;
use crate::widget::{EventHandling, HANDLED, UNHANDLED};
#[derive(Clone, Debug)]
#[must_use]
pub struct Tick {
data: Arc<TickData>,
handled_keys: HashSet<Key>,
}
impl Tick {
pub fn rendered(&self) {
self.data.rendered_frame.fetch_add(1, Ordering::AcqRel);
self.data.sync.notify_one();
}
#[must_use]
pub fn key_input(&self, input: &KeyEvent) -> EventHandling {
let mut state = self.data.state();
if input.state.is_pressed() {
state.input.keys.insert(input.logical_key.clone());
} else {
state.input.keys.remove(&input.logical_key);
}
drop(state);
if self.handled_keys.contains(&input.logical_key) {
HANDLED
} else {
UNHANDLED
}
}
}
#[derive(Default, Debug)]
pub struct WatchedInput {
pub keys: HashSet<Key>,
}
#[derive(Debug)]
struct TickData {
state: Mutex<TickState>,
period: Duration,
sync: Condvar,
rendered_frame: AtomicUsize,
}
impl TickData {
fn state(&self) -> MutexGuard<'_, TickState> {
self.state
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
}
}
#[derive(Debug)]
struct TickState {
last_time: Instant,
next_target: Instant,
keep_running: bool,
frame: usize,
input: WatchedInput,
}
impl Tick {
pub fn new<F>(tick_every: Duration, tick: F) -> Self
where
F: FnMut(Duration, &WatchedInput) + Send + 'static,
{
let now = Instant::now();
let data = Arc::new(TickData {
state: Mutex::new(TickState {
last_time: now,
next_target: now,
keep_running: true,
frame: 0,
input: WatchedInput::default(),
}),
period: tick_every,
sync: Condvar::new(),
rendered_frame: AtomicUsize::new(0),
});
std::thread::spawn({
let data = data.clone();
move || tick_loop(&data, tick)
});
Self {
data,
handled_keys: HashSet::new(),
}
}
pub fn fps<F>(frames_per_second: u32, tick: F) -> Self
where
F: FnMut(Duration, &WatchedInput) + Send + 'static,
{
Self::new(Duration::from_secs(1) / frames_per_second, tick)
}
pub fn handled_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {
self.handled_keys.extend(keys);
self
}
}
fn tick_loop<F>(data: &TickData, mut tick: F)
where
F: FnMut(Duration, &WatchedInput),
{
let mut state = data.state();
while state.keep_running {
let mut now = Instant::now();
match state.next_target.checked_duration_since(now) {
Some(remaining) if remaining > Duration::ZERO => {
drop(state);
std::thread::sleep(remaining);
state = data.state();
now = Instant::now();
}
_ => {}
}
let elapsed = now
.checked_duration_since(state.last_time)
.expect("instant never decreases");
state.frame += 1;
// TODO we need a way to batch updates for a context so that during a
// tick, no changed values trigger a redraw until we are done with the
// tick. Otherwise, a frame may start being rendered while we're still
// evaluating the tick since it's in its own thread.
tick(elapsed, &state.input);
state.next_target = (state.next_target + data.period).max(now);
state.last_time = now;
// Wait for a frame to be rendered.
while state.keep_running {
let current_frame = data.rendered_frame.load(Ordering::Acquire);
if state.frame == current_frame {
state = data
.sync
.wait(state)
.map_or_else(PoisonError::into_inner, |g| g);
} else {
break;
}
}
}
}

View file

@ -11,6 +11,7 @@ use crate::kludgine::figures::units::UPx;
use crate::kludgine::figures::Size;
use crate::kludgine::tilemap;
use crate::kludgine::tilemap::TileMapFocus;
use crate::tick::Tick;
use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
use crate::ConstraintLimit;
@ -21,6 +22,7 @@ pub struct TileMap<Layers> {
focus: Value<TileMapFocus>,
key: Option<Callback<Key, EventHandling>>,
zoom: f32,
tick: Option<Tick>,
}
impl<Layers> TileMap<Layers> {
@ -30,6 +32,7 @@ impl<Layers> TileMap<Layers> {
focus: Value::Constant(TileMapFocus::default()),
zoom: 1.,
key: None,
tick: None,
}
}
@ -53,6 +56,11 @@ impl<Layers> TileMap<Layers> {
self.key = Some(Callback::new(key));
self
}
pub fn tick(mut self, tick: Tick) -> Self {
self.tick = Some(tick);
self
}
}
impl<Layers> Widget for TileMap<Layers>
@ -66,6 +74,10 @@ where
let focus = self.focus.get();
self.layers
.map(|layers| tilemap::draw(layers, focus, self.zoom, &mut context.graphics));
if let Some(tick) = &self.tick {
tick.rendered();
}
}
fn measure(
@ -99,35 +111,18 @@ where
_device_id: DeviceId,
input: KeyEvent,
_is_synthetic: bool,
context: &mut EventContext<'_, '_>,
_context: &mut EventContext<'_, '_>,
) -> EventHandling {
if let Some(tick) = &self.tick {
tick.key_input(&input)?;
}
if !input.state.is_pressed() {
return UNHANDLED;
}
if let Some(on_key) = &mut self.key {
on_key.invoke(input.logical_key.clone())?;
}
self.focus.map_mut(|focus| {
if let TileMapFocus::Point(focus) = focus {
match input.logical_key {
Key::ArrowLeft => {
focus.x -= 1;
}
Key::ArrowRight => {
focus.x += 1;
}
Key::ArrowUp => {
focus.y -= 1;
}
Key::ArrowDown => {
focus.y += 1;
}
_ => {}
}
}
});
context.set_needs_redraw();
HANDLED
UNHANDLED
}
}

View file

@ -237,9 +237,11 @@ where
.is_some();
drop(target);
if !handled && !input.state.is_pressed() {
if !handled {
match input.physical_key {
KeyCode::KeyW if window.modifiers().state().primary() => {
KeyCode::KeyW
if window.modifiers().state().primary() && dbg!(input.state).is_pressed() =>
{
if self.request_close(&mut window) {
window.set_needs_redraw();
}