Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

22 events #26

Merged
merged 8 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ jobs:
setup_on_reload,
setup_in_state,
replacable_state,
update_reloadable_event,
remote,
asset,
]
Expand Down
3 changes: 1 addition & 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, states and resources (w/ some limitations)
- Define the reloadable areas of your game explicitly - which can include systems, components, states, events 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,6 @@ Fuller documentation is available at: <https://lee-orr.github.io/dexterous_devel
## Known issues

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

## Installation

Expand Down
1 change: 1 addition & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## UPCOMING

- add support for hot re-loading states
- add support for hot re-loading events

## Version 0.0.11

Expand Down
5 changes: 5 additions & 0 deletions dexterous_developer_internal/src/bevy_support/cold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ impl<'a> ReloadableApp for ReloadableAppContents<'a> {
self
}

fn add_event<T: ReplacableEvent>(&mut self) -> &mut Self {
self.0.add_event::<T>();
self
}

fn add_state<S: super::ReplacableState>(&mut self) -> &mut Self {
self.0.add_state::<S>();
self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ impl<'a> crate::ReloadableApp for ReloadableAppContents<'a> {

self
}

fn add_event<T: ReplacableEvent>(&mut self) -> &mut Self {
self.insert_replacable_resource::<Events<T>>()
.add_systems(First, Events::<T>::update_system)
}
}

fn element_selection_condition(name: &'static str) -> impl Fn(Option<Res<ReloadSettings>>) -> bool {
Expand Down
17 changes: 17 additions & 0 deletions dexterous_developer_internal/src/bevy_support/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ impl<T: ReplacableResource> CustomReplacableResource for T {
pub trait ReplacableComponent: Component + Serialize + DeserializeOwned + Default {
fn get_type_name() -> &'static str;
}
pub trait ReplacableEvent: Event + Serialize + DeserializeOwned {
fn get_type_name() -> &'static str;
}

pub trait ReplacableState: States + Serialize + DeserializeOwned {
fn get_type_name() -> &'static str;
Expand Down Expand Up @@ -66,6 +69,19 @@ impl<S: ReplacableState> CustomReplacableResource for NextState<S> {
}
}

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

fn to_vec(&self) -> anyhow::Result<Vec<u8>> {
Ok(vec![])
}

fn from_slice(_: &[u8]) -> anyhow::Result<Self> {
Ok(Self::default())
}
}
pub(crate) mod private {
pub trait ReloadableAppSealed {}
}
Expand All @@ -88,6 +104,7 @@ pub trait ReloadableApp: private::ReloadableAppSealed {
state: S,
systems: impl IntoSystemConfigs<M>,
) -> &mut Self;
fn add_event<T: ReplacableEvent>(&mut self) -> &mut Self;
fn add_state<S: ReplacableState>(&mut self) -> &mut Self;
}

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, state and resources (w/ some limitations)
- Define the reloadable areas of your game explicitly - which can include systems, components, state, events 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
25 changes: 25 additions & 0 deletions docs/src/reloadables.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,28 @@ impl ReplacableState for AppState {
Note that unlike `ReplacableResource` or `ReplacableComponent`, with `ReplacableState` you need to give it a name as well as giving a name for the `NextState<S>` resource it'll create.

You can then add the state using `.add_state::<ReplacableComponent>()`.

## Replacable Event

You can also create replacable Events. Here you implement the `ReplacableEvent` trait:

```rust
#[derive(Event, Clone, Debug, Serialize, Deserialize)]
pub enum AppEvent {
Text(String),
Shout(String)
}


impl ReplacableEvent for AppEvent {
fn get_type_name() -> &'static str {
"app-event"
}
}


```

Note that when events get replaced it *resets the event queue* - so all existing events will be cleared! Since as a rule events only persist to the next frame generally, this shouldn't be too much of an issue - depending on when you trigger the reload.

You can then add the state using `.add_event::<ReplacableEvent>()`.
45 changes: 45 additions & 0 deletions testing/dexterous_developer_tests/src/insert_replacable_event.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use bevy::prelude::*;
use dexterous_developer::*;
use serde::{Deserialize, Serialize};
use crate::shared::{StdInput};

fn update(input: Res<StdInput>, mut event: EventWriter<AppEvent>) {
let text = input.0.as_str();
println!("Got: {text}");
event.send(AppEvent::Text(text.to_string()))
}

fn read(input: Res<StdInput>, mut event: EventReader<AppEvent>) {
for event in event.iter() {
let text = match event {
AppEvent::Text(s) => format!("Text - {s}"),
};
println!("Event: {text}");
}
}



fn startup() {
println!("Press Enter to Progress, or type 'exit' to exit");
}

#[derive(Event, Clone, Debug, Serialize, Deserialize)]
pub enum AppEvent {
Text(String),
}


impl ReplacableEvent for AppEvent {
fn get_type_name() -> &'static str {
"app-event"
}
}

#[dexterous_developer_setup]
pub fn reloadable(app: &mut ReloadableAppContents) {
app
.add_event::<AppEvent>()
.add_systems(Startup, startup)
.add_systems(Update, (update, read).chain());
}
65 changes: 65 additions & 0 deletions testing/dexterous_developer_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,66 @@ async fn can_run_existing(path: &Path) {
process.exit().await;
}

async fn update_reloadable_event() {
let mut project: TestProject =
TestProject::new("reloadables_test", "insert_replacable_event").unwrap();
let mut process = project.run_hot_cli().await.unwrap();

process.is_ready().await;

process.send("\n").expect("Failed to send empty line");

process.wait_for_lines(&["Ran Update"]).await;

project
.write_file(
PathBuf::from("src/update.rs").as_path(),
include_str!("./insert_replacable_event.txt"),
)
.expect("Couldn't update file");

process.has_updated().await;

process.send("test\n").expect("Failed to send empty line");

process
.wait_for_lines(&["Got: test", "Event: Text - test"])
.await;

process
.send("shout: test\n")
.expect("Failed to send empty line");

process
.wait_for_lines(&["Got: shout: test", "Event: Text - shout: test"])
.await;

project
.write_file(
PathBuf::from("src/update.rs").as_path(),
include_str!("./update_replacable_event.txt"),
)
.expect("Couldn't update file");

process.has_updated().await;

process.send("test\n").expect("Failed to send empty line");

process
.wait_for_lines(&["Got: test", "Event: Text - test"])
.await;

process
.send("shout: test\n")
.expect("Failed to send empty line");

process
.wait_for_lines(&["Got: shout: test", "Event: Shout - test"])
.await;

process.exit().await;
}

async fn replacable_state() {
let mut project: TestProject =
TestProject::new("reloadables_test", "insert_replacable_state").unwrap();
Expand Down Expand Up @@ -709,6 +769,9 @@ pub async fn run_tests() {
"setup_in_state" => {
run_setup_in_state().await;
}
"update_reloadable_event" => {
update_reloadable_event().await;
}
"replacable_state" => {
replacable_state().await;
}
Expand Down Expand Up @@ -752,6 +815,8 @@ pub async fn run_tests() {
println!("clear_on_reload");
println!("setup_on_reload");
println!("setup_in_state");
println!("update_reloadable_event");
println!("replacable_state");
std::process::exit(1)
}
}
Expand Down
50 changes: 50 additions & 0 deletions testing/dexterous_developer_tests/src/update_replacable_event.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::shared::StdInput;
use bevy::prelude::*;
use dexterous_developer::*;
use serde::{Deserialize, Serialize};

fn update(input: Res<StdInput>, mut event: EventWriter<AppEvent>) {
let text = input.0.as_str();
println!("Got: {text}");
let e = if text.starts_with("shout:") {
let text_a = text.replace("shout:", "");
let text = text_a.trim();
AppEvent::Shout(text.to_string())
} else {
AppEvent::Text(text.to_string())
};
event.send(e)
}

fn read(input: Res<StdInput>, mut event: EventReader<AppEvent>) {
for event in event.iter() {
let text = match event {
AppEvent::Text(s) => format!("Text - {s}"),
AppEvent::Shout(s) => format!("Shout - {s}"),
};
println!("Event: {text}");
}
}

fn startup() {
println!("Press Enter to Progress, or type 'exit' to exit");
}

#[derive(Event, PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum AppEvent {
Text(String),
Shout(String),
}

impl ReplacableEvent for AppEvent {
fn get_type_name() -> &'static str {
"app-event"
}
}

#[dexterous_developer_setup]
pub fn reloadable(app: &mut ReloadableAppContents) {
app.add_event::<AppEvent>()
.add_systems(Startup, startup)
.add_systems(Update, (update, read).chain());
}