mirror of
https://github.com/danbulant/cushy
synced 2026-06-20 23:11:12 +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",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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<'_, '_>) {
|
||||
|
|
|
|||
|
|
@ -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
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::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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue