Skip to content

Commit

Permalink
feat: scene-based frozen lake
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Jul 5, 2024
1 parent 342d816 commit 14e9fd6
Show file tree
Hide file tree
Showing 23 changed files with 350 additions and 214 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/beet_core/src/steer/steer_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::prelude::*;
use bevy::prelude::*;

#[derive(Default, Clone, Component, Reflect)]
#[reflect(Component)]
/// Default marker for agents that should be considered
/// in group steering actions.
pub struct GroupSteerAgent;
Expand Down
1 change: 1 addition & 0 deletions crates/beet_core/src/steer/steer_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl Plugin for SteerPlugin {
.register_type::<ArriveRadius>()
.register_type::<WanderParams>()
.register_type::<GroupParams>()
.register_type::<GroupSteerAgent>()
/*_*/;

let world = app.world_mut();
Expand Down
7 changes: 1 addition & 6 deletions crates/beet_ecs/src/action/action_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,7 @@ impl<
// Self: ActionMeta,
{
fn build(&self, app: &mut App) {
app.init_resource::<AppTypeRegistry>();
let mut registry =
app.world_mut().resource::<AppTypeRegistry>().write();
registry.register::<T>();

drop(registry);
app.register_type::<T>();
build_common::<T>(app);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::marker::PhantomData;

/// Removes a component on the agent when this behavior starts running.
#[derive(PartialEq, Deref, DerefMut, Debug, Clone, Component, Reflect)]
#[reflect(Component)]
pub struct RemoveAgentOnRun<T: GenericActionComponent>(
#[reflect(ignore)] pub PhantomData<T>,
);
Expand Down
1 change: 1 addition & 0 deletions crates/beet_examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ web-sys.workspace = true

[dev-dependencies]
anyhow.workspace = true
rayon = "1.10.0"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio.workspace = true
Expand Down
16 changes: 15 additions & 1 deletion crates/beet_examples/examples/build_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use beet_examples::scenes;
use bevy::ecs::schedule::SystemConfigs;
use bevy::prelude::*;
mod utils;
use rayon::prelude::*;



Expand Down Expand Up @@ -33,9 +34,22 @@ fn main() -> Result<()> {
SceneItem::new("seek-3d", scenes::seek_3d),
SceneItem::new("fetch-scene", scenes::fetch_scene),
SceneItem::new("fetch-npc", scenes::fetch_npc),
// frozen-lake
SceneItem::new(
"frozen-lake-scene",
scenes::frozen_lake::frozen_lake_scene,
),
SceneItem::new(
"frozen-lake-train",
scenes::frozen_lake::frozen_lake_train,
),
SceneItem::new(
"frozen-lake-run",
scenes::frozen_lake::frozen_lake_run,
),
],
}]
.into_iter()
.into_par_iter()
.map(|project| project.save())
.collect::<Result<Vec<_>>>()?;
Ok(())
Expand Down
7 changes: 7 additions & 0 deletions crates/beet_examples/src/components/ui_terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ impl Plugin for UiTerminalPlugin {
.add_systems(Update, (
log_log_on_run.pipe(append_text),
log_user_input.pipe(append_text),
log_app_ready.pipe(append_text),
parse_text_input
))
.add_systems(
Expand Down Expand Up @@ -91,6 +92,12 @@ fn log_user_input(mut events: EventReader<OnUserMessage>) -> Vec<String> {
.map(|ev| format!("User: {}", ev.0.to_string()))
.collect()
}
fn log_app_ready(mut events: EventReader<AppReady>) -> Vec<String> {
events
.read()
.map(|_ev| format!("Event: AppReady"))
.collect()
}

fn get_top_pos(node: &Node, parent: &Node) -> f32 {
let items_height = node.size().y;
Expand Down
1 change: 1 addition & 0 deletions crates/beet_examples/src/components/wrap_around.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub const DEFAULT_WRAPAROUND_HALF_EXTENTS: f32 = 1.;


#[derive(Debug, Clone, Resource, PartialEq, Reflect)]
#[reflect(Resource)]
pub struct WrapAround {
pub half_extents: Vec3,
}
Expand Down
24 changes: 14 additions & 10 deletions crates/beet_examples/src/plugins/example_plugin_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ use bevy::prelude::*;
///
pub struct ExamplePluginFull;

impl Plugin for ExamplePluginFull{
fn build(&self, app: &mut App) {
app.add_plugins((
ExampleDefaultPlugins,
DefaultBeetPlugins,
ExamplePlugins,
));
}
impl Plugin for ExamplePluginFull {
fn build(&self, app: &mut App) {
app.add_plugins((
ExampleDefaultPlugins,
DefaultBeetPlugins,
ExamplePlugins,
));
}
}

#[derive(Default)]
Expand All @@ -39,6 +39,7 @@ impl PluginGroup for ExamplePlugins {
.add(ExampleReplicatePlugin)
.add(ExampleMlPlugin)
.add(BundlePlaceholderPlugin)
.add(FrozenLakePlugin)
}
}

Expand All @@ -48,10 +49,14 @@ pub struct ExampleMlPlugin;
impl Plugin for ExampleMlPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
// sentence selector
BertPlugin::default(),
ActionPlugin::<InsertOnAssetEvent<RunResult, Bert>>::default(),
AssetPlaceholderPlugin::<Bert>::default(),
ReadyOnAssetLoadPlugin::<Bert>::default(),
// qtables (frozen lake)
AssetPlaceholderPlugin::<QTable<GridPos,GridDirection>>::default(),
ReadyOnAssetLoadPlugin::<QTable<GridPos,GridDirection>>::default()
));
}
}
Expand Down Expand Up @@ -111,7 +116,6 @@ impl Plugin for Example3dPlugin {
FindSentenceSteerTarget<Collectable>,
RemoveAgentOnRun<Sentence>,
RemoveAgentOnRun<SteerTarget>,
)>::default())
/*-*/;
)>::default());
}
}
55 changes: 55 additions & 0 deletions crates/beet_examples/src/scenes/frozen_lake/frozen_lake_run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use super::*;
use crate::prelude::*;
use beet::prelude::*;
use bevy::prelude::*;
use std::time::Duration;

pub fn frozen_lake_run(mut commands: Commands) {
let map = FrozenLakeMap::default_four_by_four();
let grid_to_world =
GridToWorld::from_frozen_lake_map(&map, FROZEN_LAKE_SCENE_SCALE);

// agent
let agent_grid_pos = map.agent_position();
let agent_pos = grid_to_world.world_pos(*agent_grid_pos);
let object_scale = Vec3::splat(grid_to_world.cell_width * 0.5);

commands
.spawn((
Name::new("Inference Agent"),
BundlePlaceholder::Scene(frozen_lake_assets::CHARACTER.into()),
Transform::from_translation(agent_pos).with_scale(object_scale),
grid_to_world.clone(),
agent_grid_pos,
GridDirection::sample(),
))
.with_children(|parent| {
let agent = parent.parent_entity();

parent
.spawn((
Name::new("Inference Behavior"),
// Running,
InsertOnTrigger::<AppReady,Running>::default(),
SequenceSelector,
Repeat::default(),
))
.with_children(|parent| {
parent.spawn((
Name::new("Get next action"),
TargetAgent(agent),
AssetLoadBlockAppReady,
AssetPlaceholder::<FrozenLakeQTable>::new(
"ml/frozen_lake_qtable.ron",
),
ReadQPolicy::<FrozenLakeQTable>::default(),
));
parent.spawn((
Name::new("Perform action"),
TranslateGrid::new(Duration::from_secs(1)),
TargetAgent(agent),
RunTimer::default(),
));
});
});
}
55 changes: 55 additions & 0 deletions crates/beet_examples/src/scenes/frozen_lake/frozen_lake_scene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::prelude::*;
use beet::prelude::*;
use bevy::prelude::*;


pub const FROZEN_LAKE_SCENE_SCALE: f32 = 1.;


pub fn frozen_lake_scene(mut commands: Commands) {
commands.spawn((
CameraDistance::new(FROZEN_LAKE_SCENE_SCALE * 0.7),
BundlePlaceholder::Camera3d,
));

let map = FrozenLakeMap::default_four_by_four();
let grid_to_world =
GridToWorld::from_frozen_lake_map(&map, FROZEN_LAKE_SCENE_SCALE);

let tile_scale = Vec3::splat(grid_to_world.cell_width);
for x in 0..map.num_cols() {
for y in 0..map.num_rows() {
let mut pos = grid_to_world.world_pos(UVec2::new(x, y));
pos.y -= grid_to_world.cell_width;
commands.spawn((
Transform::from_translation(pos).with_scale(tile_scale),
BundlePlaceholder::Scene(frozen_lake_assets::TILE.into()),
));
}
}

let object_scale = Vec3::splat(grid_to_world.cell_width * 0.5);

for (index, cell) in map.cells().iter().enumerate() {
let grid_pos = map.index_to_position(index);
let mut pos = grid_to_world.world_pos(grid_pos);
match cell {
FrozenLakeCell::Hole => {
pos.y += grid_to_world.cell_width * 0.25; // this asset is a bit too low
commands.spawn((
Transform::from_translation(pos).with_scale(object_scale),
BundlePlaceholder::Scene(frozen_lake_assets::HAZARD.into()),
));
}
FrozenLakeCell::Goal => {
commands.spawn((
BundlePlaceholder::Scene(frozen_lake_assets::GOAL.into()),
Transform::from_translation(pos).with_scale(object_scale),
));
}
FrozenLakeCell::Ice => { /* already spawned on the grid */ }
FrozenLakeCell::Agent => { /*spawns on episode */ }
}
{}
}
}
14 changes: 14 additions & 0 deletions crates/beet_examples/src/scenes/frozen_lake/frozen_lake_train.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use beet::prelude::*;
use bevy::prelude::*;
use super::*;

pub fn frozen_lake_train(mut commands: Commands) {

let map = FrozenLakeMap::default_four_by_four();
let params = FrozenLakeEpParams {
learn_params: default(),
grid_to_world: GridToWorld::from_frozen_lake_map(&map, FROZEN_LAKE_SCENE_SCALE),
map,
};
commands.spawn((RlSession::new(params), FrozenLakeQTable::default()));
}
84 changes: 84 additions & 0 deletions crates/beet_examples/src/serde_utils/save_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub fn save_scene(
.extract_resources()
.build();

assert_scene_match(&filename, world, &scene)?;

let type_registry = world.resource::<AppTypeRegistry>();
let serialized_scene = scene.serialize(&type_registry.read())?;

Expand All @@ -45,5 +47,87 @@ pub fn save_scene(

let mut file = File::create(filename)?;
file.write(serialized_scene.as_bytes())?;

Ok(())
}


const ALLOWED_IGNORES: &[&str] = &[
"bevy_ui::ui_node::BorderRadius",
"bevy_animation::transition::AnimationTransitions",
];

fn assert_scene_match(
filename: &str,
world: &World,
scene: &DynamicScene,
) -> Result<()> {
const NUM_IGNORED_RESOURCES: usize = 155;

let mut issues = Vec::<String>::new();

let num_entities_world = world.iter_entities().count();
let num_entities_scene = scene.entities.len();
if num_entities_world != num_entities_scene {
issues.push(
format!("Entity count mismatch: Expected {num_entities_world}, got {num_entities_scene}"));
}
let num_resources_world =
world.iter_resources().count() - NUM_IGNORED_RESOURCES;
let num_resources_scene = scene.resources.len();
if num_resources_world != num_resources_scene {
issues.push(
format!("Resource count mismatch: Expected {num_resources_world}, got {num_resources_scene}"));
}
for (resource, _) in world.iter_resources() {
let resource_scene = scene.resources.iter().find(|r| {
r.get_represented_type_info()
.expect("found resource without typeinfo")
.type_id() == resource
.type_id()
.expect("found resource without typeid")
});
if resource_scene.is_none() {
issues.push(format!("Resource missing: {}", resource.name()));
}
}

for entity in world.iter_entities() {
let entity_scene = scene
.entities
.iter()
.find(|e| e.entity == entity.id())
.expect("just checked entity count");

for component in world.inspect_entity(entity.id()).iter() {
let num_components_world = world.inspect_entity(entity.id()).len();
let num_components_scene = entity_scene.components.len();
if num_components_world != num_components_scene {
// issues.push(format!(
// "Component count mismatch: Expected {num_components_world}, got {num_components_scene}"
// ));
// println!(
// "{filename}: Component count mismatch: Expected {num_components_world}, got {num_components_scene}"
// );
}

let component_scene = entity_scene.components.iter().find(|c| {
c.get_represented_type_info()
.expect("found component without typeinfo")
.type_id() == component
.type_id()
.expect("found component without typeid")
});
if component_scene.is_none()
&& !ALLOWED_IGNORES.contains(&component.name())
{
issues.push(format!("Component missing: {}", component.name()));
}
}
}
if issues.len() > 0 {
anyhow::bail!("{filename}: issues found:\n{}", issues.join("\n"))
} else {
Ok(())
}
}
Loading

0 comments on commit 14e9fd6

Please sign in to comment.