Skip to content

Commit

Permalink
State-support (#25)
Browse files Browse the repository at this point in the history
* add reloadable state

Signed-off-by: lee-orr <lee-orr@users.noreply.github.com>

* readme and docs

Signed-off-by: lee-orr <lee-orr@users.noreply.github.com>

* fix intro

Signed-off-by: lee-orr <lee-orr@users.noreply.github.com>

---------

Signed-off-by: lee-orr <lee-orr@users.noreply.github.com>
  • Loading branch information
lee-orr authored Oct 12, 2023
1 parent 6dd85ea commit 88fcc4a
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 26 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,6 @@ jobs:
hot,
edit,
launcher,
remote,
asset,
initialize_resource,
update_resource,
reset_resource,
Expand All @@ -198,6 +196,9 @@ jobs:
clear_on_reload,
setup_on_reload,
setup_in_state,
replacable_state,
remote,
asset,
]
runs-on: ${{matrix.os}}
steps:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Fuller documentation is available at: <https://lee-orr.github.io/dexterous_devel

## Features

- Define the reloadable areas of your game explicitly - which can include systems, components and resources (w/ some limitations)
- Define the reloadable areas of your game explicitly - which can include systems, components, states and resources (w/ some limitations)
- Reset resources to a default or pre-determined value upon reload
- Serialize/deserialize your reloadable resources & components, allowing you to evolve their schemas so long as they are compatible with the de-serializer (using rmp_serde)
- Mark entities to get removed on hot reload
Expand All @@ -23,7 +23,7 @@ Fuller documentation is available at: <https://lee-orr.github.io/dexterous_devel
## Known issues

- Won't work on mobile or WASM
- events and states still need to be pre-defined
- events still need to be pre-defined

## Installation

Expand Down
2 changes: 2 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## UPCOMING

- add support for hot re-loading states

## Version 0.0.11

- generate a temporary manifest with dylib - avoiding the need to set that up in advance
Expand Down
54 changes: 49 additions & 5 deletions dexterous_developer_example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ use dexterous_developer::{
dexterous_developer_setup, hot_bevy_main, InitialPlugins, ReloadableApp, ReloadableAppContents,
ReloadableElementsSetup, ReplacableComponent, ReplacableResource,
};
use dexterous_developer::{ReloadMode, ReloadSettings, ReloadableSetup};
use dexterous_developer::{ReloadMode, ReloadSettings, ReloadableSetup, ReplacableState};
use serde::{Deserialize, Serialize};

#[hot_bevy_main]
pub fn bevy_main(initial_plugins: impl InitialPlugins) {
App::new()
.add_plugins(initial_plugins.initialize::<DefaultPlugins>())
.add_state::<AppState>()
.add_plugins(WorldInspectorPlugin::new())
.add_systems(Startup, setup)
.setup_reloadable_elements::<reloadable>()
Expand All @@ -36,21 +35,34 @@ pub fn bevy_main(initial_plugins: impl InitialPlugins) {
.run();
}

#[derive(States, PartialEq, Eq, Clone, Copy, Debug, Hash, Default)]
#[derive(States, PartialEq, Eq, Clone, Copy, Debug, Hash, Default, Serialize, Deserialize)]
pub enum AppState {
#[default]
State,
AnotherState,
State,
TwoSpheres,
}

impl ReplacableState for AppState {
fn get_type_name() -> &'static str {
"app-state"
}

fn get_next_type_name() -> &'static str {
"next-app-state"
}
}

#[dexterous_developer_setup(first_reloadable)]
fn reloadable(app: &mut ReloadableAppContents) {
app.add_state::<AppState>();
println!("Setting up reloadabless #1");
app.add_systems(Update, (move_cube, toggle));
println!("Reset Setup");
app.reset_setup::<Cube, _>(setup_cube);
println!("Reset Setup In State");
app.reset_setup_in_state::<Sphere, AppState, _>(AppState::AnotherState, setup_sphere);
app.reset_setup_in_state::<Sphere, AppState, _>(AppState::TwoSpheres, setup_two_spheres);
println!("Done");
}

Expand Down Expand Up @@ -147,6 +159,37 @@ fn setup_sphere(
));
}

fn setup_two_spheres(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Sphere,
PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.2,
..Default::default()
})),
material: materials.add(Color::PINK.into()),
transform: Transform::from_xyz(1.0, 0.5, 0.0),
..default()
},
));
commands.spawn((
Sphere,
PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.2,
..Default::default()
})),
material: materials.add(Color::PINK.into()),
transform: Transform::from_xyz(-1.0, 0.5, 0.0),
..default()
},
));
}

#[allow(unused)]
fn setup(
mut commands: Commands,
Expand Down Expand Up @@ -201,7 +244,8 @@ fn toggle(input: Res<Input<KeyCode>>, mut commands: Commands, current: Res<State
if input.just_pressed(KeyCode::Space) {
let next = match current.get() {
AppState::State => AppState::AnotherState,
AppState::AnotherState => AppState::State,
AppState::AnotherState => AppState::TwoSpheres,
AppState::TwoSpheres => AppState::State,
};
commands.insert_resource(NextState(Some(next)));
}
Expand Down
7 changes: 6 additions & 1 deletion dexterous_developer_internal/src/bevy_support/cold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl<'a> ReloadableApp for ReloadableAppContents<'a> {
self
}

fn insert_replacable_resource<R: super::ReplacableResource>(&mut self) -> &mut Self {
fn insert_replacable_resource<R: super::CustomReplacableResource>(&mut self) -> &mut Self {
self.0.init_resource::<R>();
self
}
Expand Down Expand Up @@ -69,4 +69,9 @@ impl<'a> ReloadableApp for ReloadableAppContents<'a> {
.add_systems(OnExit(state), clear_marked_system::<C>);
self
}

fn add_state<S: super::ReplacableState>(&mut self) -> &mut Self {
self.0.add_state::<S>();
self
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bevy::{
ecs::schedule::ScheduleLabel,
ecs::schedule::common_conditions::run_once,
ecs::schedule::{run_enter_schedule, ScheduleLabel},
prelude::*,
utils::{HashMap, HashSet},
};
Expand Down Expand Up @@ -68,7 +69,7 @@ impl<'a> crate::ReloadableApp for ReloadableAppContents<'a> {
self
}

fn insert_replacable_resource<R: ReplacableResource>(&mut self) -> &mut Self {
fn insert_replacable_resource<R: CustomReplacableResource>(&mut self) -> &mut Self {
let name = R::get_type_name();
if !self.resources.contains(name) {
self.resources.insert(name.to_string());
Expand Down Expand Up @@ -176,6 +177,21 @@ impl<'a> crate::ReloadableApp for ReloadableAppContents<'a> {
),
)
}

fn add_state<S: ReplacableState>(&mut self) -> &mut Self {
self.insert_replacable_resource::<State<S>>()
.insert_replacable_resource::<NextState<S>>()
.add_systems(
StateTransition,
((
run_enter_schedule::<S>.run_if(run_once()),
apply_state_transition::<S>,
)
.chain(),),
);

self
}
}

fn element_selection_condition(name: &'static str) -> impl Fn(Option<Res<ReloadSettings>>) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@ use bevy::{
utils::HashMap,
};

use crate::{ReplacableComponent, ReplacableResource};
use crate::{CustomReplacableResource, ReplacableComponent};

#[derive(Resource, Default)]
pub struct ReplacableResourceStore {
map: HashMap<String, Vec<u8>>,
}

pub fn serialize_replacable_resource<R: ReplacableResource>(
pub fn serialize_replacable_resource<R: CustomReplacableResource>(
mut store: ResMut<ReplacableResourceStore>,
resource: Option<Res<R>>,
mut commands: Commands,
) {
let Some(resource) = resource else {
return;
};
if let Ok(v) = rmp_serde::to_vec(resource.as_ref()) {
if let Ok(v) = resource.to_vec() {
store.map.insert(R::get_type_name().to_string(), v);
}

commands.remove_resource::<R>();
}

pub fn deserialize_replacable_resource<R: ReplacableResource>(
pub fn deserialize_replacable_resource<R: CustomReplacableResource>(
store: Res<ReplacableResourceStore>,
mut commands: Commands,
) {
Expand All @@ -34,7 +34,7 @@ pub fn deserialize_replacable_resource<R: ReplacableResource>(
let v: R = store
.map
.get(name)
.and_then(|v| rmp_serde::from_slice(v.as_slice()).ok())
.and_then(|v| R::from_slice(v).ok())
.unwrap_or_default();

commands.insert_resource(v);
Expand Down
60 changes: 59 additions & 1 deletion dexterous_developer_internal/src/bevy_support/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,67 @@ pub trait ReplacableResource: Resource + Serialize + DeserializeOwned + Default
fn get_type_name() -> &'static str;
}

pub trait CustomReplacableResource: Resource + Default {
fn get_type_name() -> &'static str;

fn to_vec(&self) -> anyhow::Result<Vec<u8>>;

fn from_slice(val: &[u8]) -> anyhow::Result<Self>;
}

impl<T: ReplacableResource> CustomReplacableResource for T {
fn get_type_name() -> &'static str {
T::get_type_name()
}

fn to_vec(&self) -> anyhow::Result<Vec<u8>> {
Ok(rmp_serde::to_vec(self)?)
}

fn from_slice(val: &[u8]) -> anyhow::Result<Self> {
Ok(rmp_serde::from_slice(val)?)
}
}

pub trait ReplacableComponent: Component + Serialize + DeserializeOwned + Default {
fn get_type_name() -> &'static str;
}

pub trait ReplacableState: States + Serialize + DeserializeOwned {
fn get_type_name() -> &'static str;
fn get_next_type_name() -> &'static str;
}

impl<S: ReplacableState> CustomReplacableResource for State<S> {
fn get_type_name() -> &'static str {
S::get_type_name()
}

fn to_vec(&self) -> anyhow::Result<Vec<u8>> {
Ok(rmp_serde::to_vec(self.get())?)
}

fn from_slice(val: &[u8]) -> anyhow::Result<Self> {
let val = rmp_serde::from_slice(val)?;
Ok(Self::new(val))
}
}

impl<S: ReplacableState> CustomReplacableResource for NextState<S> {
fn get_type_name() -> &'static str {
S::get_next_type_name()
}

fn to_vec(&self) -> anyhow::Result<Vec<u8>> {
Ok(rmp_serde::to_vec(&self.0)?)
}

fn from_slice(val: &[u8]) -> anyhow::Result<Self> {
let val = rmp_serde::from_slice(val)?;
Ok(Self(val))
}
}

pub(crate) mod private {
pub trait ReloadableAppSealed {}
}
Expand All @@ -20,7 +77,7 @@ pub trait ReloadableApp: private::ReloadableAppSealed {
systems: impl IntoSystemConfigs<M>,
) -> &mut Self;

fn insert_replacable_resource<R: ReplacableResource>(&mut self) -> &mut Self;
fn insert_replacable_resource<R: CustomReplacableResource>(&mut self) -> &mut Self;
fn reset_resource<R: Resource + Default>(&mut self) -> &mut Self;
fn reset_resource_to_value<R: Resource + Clone>(&mut self, value: R) -> &mut Self;
fn register_replacable_component<C: ReplacableComponent>(&mut self) -> &mut Self;
Expand All @@ -31,6 +88,7 @@ pub trait ReloadableApp: private::ReloadableAppSealed {
state: S,
systems: impl IntoSystemConfigs<M>,
) -> &mut Self;
fn add_state<S: ReplacableState>(&mut self) -> &mut Self;
}

pub trait ReloadableSetup {
Expand Down
2 changes: 1 addition & 1 deletion docs/src/Intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This library provides an experimental hot reload system for Bevy.

## Features

- Define the reloadable areas of your game explicitly - which can include systems, components and resources (w/ some limitations)
- Define the reloadable areas of your game explicitly - which can include systems, components, state and resources (w/ some limitations)
- Reset resources to a default or pre-determined value upon reload
- serialize/deserialize your reloadable resources & components, allowing you to evolve their schemas so long as they are compatible with the de-serializer (using rmp_serde)
- mark entities to get removed on hot reload
Expand Down
Loading

0 comments on commit 88fcc4a

Please sign in to comment.