mirror of
https://github.com/danbulant/cushy
synced 2026-07-08 04:30:34 +00:00
Added Tick to TileMap
This commit is contained in:
parent
6acaf7ed1c
commit
04e5381187
7 changed files with 230 additions and 62 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
|
@ -435,6 +435,16 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "euclid"
|
name = "euclid"
|
||||||
version = "0.22.9"
|
version = "0.22.9"
|
||||||
|
|
@ -830,18 +840,17 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kludgine"
|
name = "kludgine"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/khonsulabs/kludgine#713200714daa16a273eed9fcaee476613e894116"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"alot",
|
"alot",
|
||||||
"appit",
|
"appit",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
|
"etagere",
|
||||||
"figures",
|
"figures",
|
||||||
"image",
|
"image",
|
||||||
"lyon_tessellation",
|
"lyon_tessellation",
|
||||||
"pollster",
|
"pollster",
|
||||||
"shelf-packer",
|
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
@ -1493,14 +1502,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shelf-packer"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://github.com/khonsulabs/shelf-packer#06246cb4f5ccd473c1d8ae49c8c07f2f61798df4"
|
|
||||||
dependencies = [
|
|
||||||
"figures",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slotmap"
|
name = "slotmap"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
|
@ -1578,6 +1579,12 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "svg_fmt"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swash"
|
name = "swash"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ alot = "0.3"
|
||||||
interner = "0.2.1"
|
interner = "0.2.1"
|
||||||
# appit = { git = "https://github.com/khonsulabs/appit" }
|
# appit = { git = "https://github.com/khonsulabs/appit" }
|
||||||
|
|
||||||
# [patch."https://github.com/khonsulabs/kludgine"]
|
[patch."https://github.com/khonsulabs/kludgine"]
|
||||||
# kludgine = { path = "../kludgine2" }
|
kludgine = { path = "../kludgine2" }
|
||||||
# [patch."https://github.com/khonsulabs/figures"]
|
# [patch."https://github.com/khonsulabs/figures"]
|
||||||
# figures = { path = "../figures" }
|
# figures = { path = "../figures" }
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,9 @@ use gooey::kludgine::figures::units::Px;
|
||||||
use gooey::kludgine::figures::{Point, Rect, Size};
|
use gooey::kludgine::figures::{Point, Rect, Size};
|
||||||
use gooey::kludgine::render::Renderer;
|
use gooey::kludgine::render::Renderer;
|
||||||
use gooey::kludgine::shapes::Shape;
|
use gooey::kludgine::shapes::Shape;
|
||||||
use gooey::kludgine::tilemap::{
|
use gooey::kludgine::tilemap::{Object, ObjectLayer, TileKind, TileMapFocus, Tiles, TILE_SIZE};
|
||||||
Object, ObjectId, ObjectLayer, TileKind, TileMapFocus, Tiles, TILE_SIZE,
|
|
||||||
};
|
|
||||||
use gooey::kludgine::Color;
|
use gooey::kludgine::Color;
|
||||||
use gooey::widget::{EventHandling, HANDLED, UNHANDLED};
|
use gooey::tick::Tick;
|
||||||
use gooey::widgets::TileMap;
|
use gooey::widgets::TileMap;
|
||||||
use gooey::{EventLoopError, Run};
|
use gooey::{EventLoopError, Run};
|
||||||
|
|
||||||
|
|
@ -35,7 +33,7 @@ fn main() -> Result<(), EventLoopError> {
|
||||||
|
|
||||||
let myself = characters.push(Player {
|
let myself = characters.push(Player {
|
||||||
color: Color::RED,
|
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));
|
let layers = Dynamic::new((Tiles::new(8, 8, TILES), characters));
|
||||||
|
|
@ -45,37 +43,43 @@ fn main() -> Result<(), EventLoopError> {
|
||||||
layer: 1,
|
layer: 1,
|
||||||
id: myself,
|
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()
|
.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)]
|
#[derive(Debug)]
|
||||||
struct Player {
|
struct Player {
|
||||||
color: Color,
|
color: Color,
|
||||||
position: Point<Px>,
|
position: Point<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object for Player {
|
impl Object for Player {
|
||||||
fn position(&self) -> Point<Px> {
|
fn position(&self) -> Point<Px> {
|
||||||
self.position
|
self.position.cast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, center: Point<Px>, zoom: f32, context: &mut Renderer<'_, '_>) {
|
fn render(&self, center: Point<Px>, zoom: f32, context: &mut Renderer<'_, '_>) {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub mod dynamic;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
pub mod names;
|
pub mod names;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
|
pub mod tick;
|
||||||
mod tree;
|
mod tree;
|
||||||
mod utils;
|
mod utils;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
|
||||||
159
src/tick.rs
Normal file
159
src/tick.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::kludgine::figures::units::UPx;
|
||||||
use crate::kludgine::figures::Size;
|
use crate::kludgine::figures::Size;
|
||||||
use crate::kludgine::tilemap;
|
use crate::kludgine::tilemap;
|
||||||
use crate::kludgine::tilemap::TileMapFocus;
|
use crate::kludgine::tilemap::TileMapFocus;
|
||||||
|
use crate::tick::Tick;
|
||||||
use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED, UNHANDLED};
|
||||||
use crate::ConstraintLimit;
|
use crate::ConstraintLimit;
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ pub struct TileMap<Layers> {
|
||||||
focus: Value<TileMapFocus>,
|
focus: Value<TileMapFocus>,
|
||||||
key: Option<Callback<Key, EventHandling>>,
|
key: Option<Callback<Key, EventHandling>>,
|
||||||
zoom: f32,
|
zoom: f32,
|
||||||
|
tick: Option<Tick>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Layers> TileMap<Layers> {
|
impl<Layers> TileMap<Layers> {
|
||||||
|
|
@ -30,6 +32,7 @@ impl<Layers> TileMap<Layers> {
|
||||||
focus: Value::Constant(TileMapFocus::default()),
|
focus: Value::Constant(TileMapFocus::default()),
|
||||||
zoom: 1.,
|
zoom: 1.,
|
||||||
key: None,
|
key: None,
|
||||||
|
tick: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +56,11 @@ impl<Layers> TileMap<Layers> {
|
||||||
self.key = Some(Callback::new(key));
|
self.key = Some(Callback::new(key));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tick(mut self, tick: Tick) -> Self {
|
||||||
|
self.tick = Some(tick);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Layers> Widget for TileMap<Layers>
|
impl<Layers> Widget for TileMap<Layers>
|
||||||
|
|
@ -66,6 +74,10 @@ where
|
||||||
let focus = self.focus.get();
|
let focus = self.focus.get();
|
||||||
self.layers
|
self.layers
|
||||||
.map(|layers| tilemap::draw(layers, focus, self.zoom, &mut context.graphics));
|
.map(|layers| tilemap::draw(layers, focus, self.zoom, &mut context.graphics));
|
||||||
|
|
||||||
|
if let Some(tick) = &self.tick {
|
||||||
|
tick.rendered();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measure(
|
fn measure(
|
||||||
|
|
@ -99,35 +111,18 @@ where
|
||||||
_device_id: DeviceId,
|
_device_id: DeviceId,
|
||||||
input: KeyEvent,
|
input: KeyEvent,
|
||||||
_is_synthetic: bool,
|
_is_synthetic: bool,
|
||||||
context: &mut EventContext<'_, '_>,
|
_context: &mut EventContext<'_, '_>,
|
||||||
) -> EventHandling {
|
) -> EventHandling {
|
||||||
|
if let Some(tick) = &self.tick {
|
||||||
|
tick.key_input(&input)?;
|
||||||
|
}
|
||||||
if !input.state.is_pressed() {
|
if !input.state.is_pressed() {
|
||||||
return UNHANDLED;
|
return UNHANDLED;
|
||||||
}
|
}
|
||||||
if let Some(on_key) = &mut self.key {
|
if let Some(on_key) = &mut self.key {
|
||||||
on_key.invoke(input.logical_key.clone())?;
|
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();
|
UNHANDLED
|
||||||
HANDLED
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,9 +237,11 @@ where
|
||||||
.is_some();
|
.is_some();
|
||||||
drop(target);
|
drop(target);
|
||||||
|
|
||||||
if !handled && !input.state.is_pressed() {
|
if !handled {
|
||||||
match input.physical_key {
|
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) {
|
if self.request_close(&mut window) {
|
||||||
window.set_needs_redraw();
|
window.set_needs_redraw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue