Skip to content

Commit 0136d32

Browse files
committed
Genericize puzzle state and animation
I need Rust v1.86.0 (currently beta) for rust-lang/rust#134540
1 parent 38336a4 commit 0136d32

File tree

14 files changed

+263
-101
lines changed

14 files changed

+263
-101
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ documentation = "https://dev.hypercubing.xyz/internals/"
2424
edition = "2024"
2525
homepage = "https://ajfarkas.dev/projects/hyperspeedcube/"
2626
repository = "https://github.com/HactarCE/Hyperspeedcube/"
27-
rust-version = "1.85.0"
27+
rust-version = "1.86.0"
2828

2929
[workspace.lints.rust]
3030
missing_docs = "warn"

crates/hyperprefs/src/filters/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::borrow::Cow;
2+
use std::collections::HashMap;
13
use std::fmt;
24
use std::sync::Arc;
3-
use std::{borrow::Cow, collections::HashMap};
45

56
use hyperpuzzle_core::{PieceMask, Puzzle};
67
use itertools::Itertools;

crates/hyperpuzzle_core/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ mod catalog;
77
mod lint;
88
mod logging;
99
mod nameable;
10+
mod nongeneric;
1011
mod puzzle;
1112
mod rgb;
1213
mod tags;
1314
mod timestamp;
15+
mod traits;
1416
pub mod util;
1517
mod version;
1618

@@ -20,10 +22,12 @@ pub use chrono;
2022
pub use lint::PuzzleLintOutput;
2123
pub use logging::*;
2224
pub use nameable::*;
25+
pub use nongeneric::*;
2326
pub use puzzle::*;
2427
pub use rgb::Rgb;
2528
pub use tags::*;
2629
pub use timestamp::Timestamp;
30+
pub use traits::*;
2731
pub use version::Version;
2832

2933
/// Unsigned integer type used for [`LayerMask`].
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use hypermath::pga;
2+
3+
use crate::{BoxDynPuzzleAnimation, PerPiece, PieceMask, PuzzleAnimation, PuzzleStateRenderData};
4+
5+
// /// Data that needs to be uploaded to the GPU before rendering.
6+
// pub enum PuzzleTypeGpuBuffers {
7+
// /// N-dimensional Euclidean mesh data.
8+
// Hypershape(),
9+
// /// No GPU data
10+
// None,
11+
// }
12+
13+
/// Puzzle render data for an N-dimensional Euclidean puzzle.
14+
pub struct NdEuclidPuzzleStateRenderData {
15+
/// Transform for each piece.
16+
pub piece_transforms: PerPiece<pga::Motor>,
17+
}
18+
impl PuzzleStateRenderData for NdEuclidPuzzleStateRenderData {}
19+
20+
#[derive(Debug, Clone)]
21+
pub struct NdEuclidPuzzleAnimation {
22+
/// Set of pieces affected by the animation.
23+
pub pieces: PieceMask,
24+
/// Initial transform of the pieces (identity, unless the move was inputted
25+
/// using a mouse drag).
26+
pub initial_transform: pga::Motor,
27+
/// Final transform for the pieces.
28+
pub final_transform: pga::Motor,
29+
}
30+
impl PuzzleAnimation for NdEuclidPuzzleAnimation {
31+
fn dyn_clone(&self) -> BoxDynPuzzleAnimation
32+
where
33+
Self: Sized,
34+
{
35+
self.clone().into()
36+
}
37+
}

crates/hyperpuzzle_core/src/puzzle/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ pub use notation::Notation;
2222
pub use piece_type_hierarchy::*;
2323
pub use puzzle_type::{PLACEHOLDER_PUZZLE, Puzzle};
2424
pub use scramble::{ScrambleParams, ScrambleProgress, ScrambleType, ScrambledPuzzle};
25-
pub use state::PuzzleState;
25+
pub use state::HypershapePuzzleState;
2626
pub use twist::LayeredTwist;

crates/hyperpuzzle_core/src/puzzle/puzzle_type.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ lazy_static! {
4141
gizmo_twists: PerGizmoFace::new(),
4242
dev_data: PuzzleDevData::new(),
4343

44-
new: Box::new(PuzzleState::new),
44+
new: Box::new(HypershapePuzzleState::new),
4545
});
4646
}
4747

@@ -99,7 +99,7 @@ pub struct Puzzle {
9999
pub dev_data: PuzzleDevData,
100100

101101
/// Constructor for a solved puzzle state.
102-
pub new: Box<dyn Send + Sync + Fn(Arc<Self>) -> PuzzleState>,
102+
pub new: Box<dyn Send + Sync + Fn(Arc<Self>) -> HypershapePuzzleState>,
103103
}
104104

105105
impl fmt::Debug for Puzzle {
@@ -138,7 +138,7 @@ impl Puzzle {
138138
self.this.upgrade().expect("`Puzzle` removed from `Arc`")
139139
}
140140
/// Constructs a new instance of the puzzle.
141-
pub fn new_solved_state(&self) -> PuzzleState {
141+
pub fn new_solved_state(&self) -> HypershapePuzzleState {
142142
(self.new)(self.arc())
143143
}
144144
/// Constructs a new scrambled instance of the puzzle.

crates/hyperpuzzle_core/src/puzzle/scramble.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicBool, AtomicU32};
33
use rand::Rng;
44
use serde::{Deserialize, Serialize};
55

6-
use super::{LayeredTwist, PuzzleState};
6+
use super::{HypershapePuzzleState, LayeredTwist};
77
use crate::Timestamp;
88

99
/// Parameters to deterministically generate a twist sequence to scramble a
@@ -96,5 +96,5 @@ pub struct ScrambledPuzzle {
9696
/// Scramble twists applied.
9797
pub twists: Vec<LayeredTwist>,
9898
/// State of the puzzle after scrambling.
99-
pub state: PuzzleState,
99+
pub state: HypershapePuzzleState,
100100
}

crates/hyperpuzzle_core/src/puzzle/state.rs

+49-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ use hypermath::prelude::*;
77
use itertools::Itertools;
88
use parking_lot::Mutex;
99

10-
use crate::{Axis, AxisInfo, LayerMask, LayeredTwist, PerAxis, PerPiece, Piece, PieceMask, Puzzle};
10+
use crate::{
11+
Axis, AxisInfo, BoxDynPuzzleAnimation, BoxDynPuzzleStateRenderData, LayerMask, LayeredTwist,
12+
NdEuclidPuzzleAnimation, NdEuclidPuzzleStateRenderData, PerAxis, PerPiece, Piece, PieceMask,
13+
Puzzle, PuzzleState,
14+
};
1115

1216
type PerCachedTransform<T> = GenericVec<CachedTransform, T>;
1317
idx_struct! {
@@ -39,7 +43,7 @@ impl CachedTransformData {
3943

4044
/// Instance of a puzzle with a particular state.
4145
#[derive(Debug, Clone)]
42-
pub struct PuzzleState {
46+
pub struct HypershapePuzzleState {
4347
/// Immutable puzzle type info.
4448
puzzle_type: Arc<Puzzle>,
4549
/// Attitude (position & rotation) of each piece.
@@ -48,7 +52,47 @@ pub struct PuzzleState {
4852
cached_transforms: Arc<Mutex<PerCachedTransform<CachedTransformData>>>,
4953
cached_transform_by_motor: Arc<Mutex<ApproxHashMap<pga::Motor, CachedTransform>>>,
5054
}
51-
impl PuzzleState {
55+
56+
impl PuzzleState for HypershapePuzzleState {
57+
fn ty(&self) -> &Arc<Puzzle> {
58+
&self.puzzle_type
59+
}
60+
61+
fn render_data(&self) -> BoxDynPuzzleStateRenderData {
62+
NdEuclidPuzzleStateRenderData {
63+
piece_transforms: self.piece_transforms(),
64+
}
65+
.into()
66+
}
67+
68+
fn render_data_with_animation(
69+
&self,
70+
anim: &BoxDynPuzzleAnimation,
71+
t: f32,
72+
) -> BoxDynPuzzleStateRenderData {
73+
let anim = anim
74+
.downcast_ref::<NdEuclidPuzzleAnimation>()
75+
.expect("invalid animation for puzzle");
76+
77+
let start = &anim.initial_transform;
78+
let end = &anim.final_transform;
79+
let m = if t == 0.0 {
80+
start.clone()
81+
} else if t == 1.0 {
82+
end.clone()
83+
} else {
84+
pga::Motor::slerp_infallible(start, end, t as _)
85+
};
86+
87+
let mut piece_transforms = self.piece_transforms();
88+
for piece in anim.pieces.iter() {
89+
piece_transforms[piece] = &m * &piece_transforms[piece];
90+
}
91+
NdEuclidPuzzleStateRenderData { piece_transforms }.into()
92+
}
93+
}
94+
95+
impl HypershapePuzzleState {
5296
/// Constructs a new instance of a puzzle.
5397
pub fn new(puzzle_type: Arc<Puzzle>) -> Self {
5498
let ident = pga::Motor::ident(puzzle_type.ndim());
@@ -62,36 +106,19 @@ impl PuzzleState {
62106
by_motor.insert(ident, CachedTransform(0));
63107
let cached_transform_by_motor = Arc::new(Mutex::new(by_motor));
64108

65-
PuzzleState {
109+
HypershapePuzzleState {
66110
puzzle_type,
67111
piece_transforms,
68112
cached_transforms,
69113
cached_transform_by_motor,
70114
}
71115
}
72-
/// Returns the puzzle type
73-
pub fn ty(&self) -> &Arc<Puzzle> {
74-
&self.puzzle_type
75-
}
76116
/// Returns the position and rotation of each piece.
77-
pub fn piece_transforms(&self) -> PerPiece<pga::Motor> {
117+
pub(crate) fn piece_transforms(&self) -> PerPiece<pga::Motor> {
78118
let cached = self.cached_transforms.lock();
79119
self.piece_transforms
80120
.map_ref(|_, &i| cached[i].motor.clone())
81121
}
82-
/// Returns the position and rotation of each piece during an arbitrary
83-
/// animation affecting a subset of pieces.
84-
pub fn partial_piece_transforms(
85-
&self,
86-
grip: &PieceMask,
87-
transform: &pga::Motor,
88-
) -> PerPiece<pga::Motor> {
89-
self.piece_transforms()
90-
.map(|piece, static_transform| match grip.contains(piece) {
91-
true => transform * static_transform,
92-
_ => static_transform.clone(),
93-
})
94-
}
95122

96123
/// Does a twist, or returns an error containing the set of pieces that
97124
/// prevented the twist.

crates/hyperpuzzle_core/src/traits.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use std::any::Any;
2+
use std::fmt;
3+
use std::sync::Arc;
4+
5+
use crate::Puzzle;
6+
7+
/// Instance of a puzzle with a particular state.
8+
///
9+
/// In order to be dyn-compatible, this trait has no associated types. Instead
10+
/// it uses `Box<dyn Any>` for rendering data. All implementors of this trait
11+
/// should explicitly document which type(s) may be returned from
12+
/// [`PuzzleState::render_data()`].
13+
pub trait PuzzleState: 'static + fmt::Debug + Clone + Send + Sync {
14+
/// Returns the puzzle type.
15+
fn ty(&self) -> &Arc<Puzzle>;
16+
17+
/// Returns data to render the current state of the puzzle.
18+
fn render_data(&self) -> BoxDynPuzzleStateRenderData;
19+
/// Returns data to render the state of the puzzle during an animation.
20+
///
21+
/// `t` ranges from 0 to 1. Motion should be perceptually linear with
22+
/// respect to `t`.
23+
///
24+
/// # Panics
25+
///
26+
/// This method may panics if passed an invalid animation.
27+
fn render_data_with_animation(
28+
&self,
29+
anim: &BoxDynPuzzleAnimation,
30+
t: f32,
31+
) -> BoxDynPuzzleStateRenderData;
32+
}
33+
34+
macro_rules! box_dyn_wrapper_struct {
35+
($vis:vis struct $struct_name:ident(Box<dyn $trait_name:ident>)) => {
36+
$vis struct $struct_name(Box<dyn $trait_name>);
37+
impl fmt::Debug for $struct_name {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
f.debug_tuple(stringify!($struct_name))
40+
.finish_non_exhaustive()
41+
}
42+
}
43+
impl<T: $trait_name> From<T> for $struct_name {
44+
fn from(value: T) -> Self {
45+
Self(Box::new(value))
46+
}
47+
}
48+
impl $struct_name {
49+
pub fn downcast<T: $trait_name>(self) -> Option<Box<T>> {
50+
(self.0 as Box<dyn Any>).downcast().ok()
51+
}
52+
pub fn downcast_ref<T: $trait_name>(&self) -> Option<&T> {
53+
(&*self.0 as &dyn Any).downcast_ref()
54+
}
55+
}
56+
};
57+
}
58+
59+
/// Marker trait for types that may be returned from
60+
/// [`PuzzleState::render_data()`].
61+
///
62+
/// Because [`Any`] is defined with a `'static` bound, implementors of this
63+
/// trait cannot borrow from the puzzle state.
64+
pub trait PuzzleStateRenderData: Any + Send + Sync {}
65+
box_dyn_wrapper_struct!(pub struct BoxDynPuzzleStateRenderData(Box<dyn PuzzleStateRenderData>));
66+
67+
/// Marker trait for types that may be used as animations.
68+
pub trait PuzzleAnimation: Any + Send + Sync {
69+
fn dyn_clone(&self) -> BoxDynPuzzleAnimation;
70+
}
71+
box_dyn_wrapper_struct!(pub struct BoxDynPuzzleAnimation(Box<dyn PuzzleAnimation>));
72+
impl Clone for BoxDynPuzzleAnimation {
73+
fn clone(&self) -> Self {
74+
self.0.dyn_clone()
75+
}
76+
}

crates/hyperpuzzle_lua/src/builder/puzzle.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl PuzzleBuilder {
132132

133133
dev_data,
134134

135-
new: Box::new(PuzzleState::new),
135+
new: Box::new(HypershapePuzzleState::new),
136136
}))
137137
}
138138
}

crates/hyperpuzzle_view/src/animations/twist.rs

+9-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use std::collections::VecDeque;
22

3-
use hypermath::pga;
43
use hyperprefs::AnimationPreferences;
5-
use hyperpuzzle_core::{PieceMask, PuzzleState};
4+
use hyperpuzzle_core::{BoxDynPuzzleAnimation, HypershapePuzzleState};
65
use web_time::Duration;
76

87
/// If at least this much of a twist is animated in one frame, just skip the
@@ -15,7 +14,7 @@ const EXP_TWIST_FACTOR: f32 = 0.5;
1514
#[derive(Debug, Default, Clone)]
1615
pub struct TwistAnimationState {
1716
/// Queue of twist animations to be displayed.
18-
queue: VecDeque<TwistAnimation>,
17+
queue: VecDeque<AnimationFromState>,
1918
/// Maximum number of animations in the queue (reset when queue is empty).
2019
queue_max: usize,
2120
/// Progress of the animation in the current twist, from 0.0 to 1.0.
@@ -57,27 +56,22 @@ impl TwistAnimationState {
5756
}
5857
}
5958

60-
pub fn push(&mut self, anim: TwistAnimation) {
59+
pub fn push(&mut self, anim: AnimationFromState) {
6160
self.queue.push_back(anim);
6261

6362
// Update queue_max.
6463
self.queue_max = std::cmp::max(self.queue_max, self.queue.len());
6564
}
6665

67-
pub fn current(&self) -> Option<(&TwistAnimation, f32)> {
66+
pub fn current(&self) -> Option<(&AnimationFromState, f32)> {
6867
Some((self.queue.front()?, self.progress))
6968
}
7069
}
7170

7271
#[derive(Debug, Clone)]
73-
pub struct TwistAnimation {
74-
/// Puzzle state before the twist.
75-
pub state: PuzzleState,
76-
/// Set of pieces affected by the twist.
77-
pub grip: PieceMask,
78-
/// Initial transform of the gripped pieces (identity, unless the move was
79-
/// inputted using a mouse drag).
80-
pub initial_transform: pga::Motor,
81-
/// Final transform for the the gripped pieces.
82-
pub final_transform: pga::Motor,
72+
pub struct AnimationFromState {
73+
/// Puzzle state before the animation.
74+
pub state: HypershapePuzzleState,
75+
/// Animation to apply to the state.
76+
pub anim: BoxDynPuzzleAnimation,
8377
}

0 commit comments

Comments
 (0)